package com.beeasy.hzbpm.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.beeasy.hzbpm.bean.*;
import com.beeasy.hzbpm.bpm.BpmMQ;
import com.beeasy.hzbpm.entity.BpmData;
import com.beeasy.hzbpm.entity.BpmInstance;
import com.beeasy.hzbpm.exception.BpmException;
import com.beeasy.hzbpm.filter.Auth;
import com.beeasy.hzbpm.work.SmartChooseOption;
import com.esotericsoftware.reflectasm.FieldAccess;
import com.github.llyb120.nami.core.Async;
import com.github.llyb120.nami.func.Arg1Function;
import com.github.llyb120.json.Arr;
import com.github.llyb120.json.Json;
import com.github.llyb120.json.Obj;
import com.github.llyb120.json.Validate;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.beetl.sql.core.SQLReady;
import org.bson.Document;
import org.bson.types.ObjectId;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.beeasy.hzbpm.bean.MongoService.db;
import static com.beeasy.hzbpm.bean.Util.getUserCacheName;
import static com.beeasy.hzbpm.bean.Util.isSu;
import static com.beeasy.hzbpm.service.WorkService.work;
import static com.github.llyb120.nami.func.Function.func;
import static com.github.llyb120.json.Json.*;

//import com.beeasy.hzbpm.entity.BpmData;

public class BpmService {

    //BpmData
    private BpmData model = null;
    public String modelId;

    private MongoCollection<Document> workflowCol = db.getCollection("workflow");
//    private String xml;

//    private BpmData bpmData = null;

    //BpmInstance
    public BpmInstance ins = null;
    private List<Document> childps;

    public static BpmService ofModel(final String modelId) {
        MongoCollection<Document> col = db.getCollection("workflow");
//        col.mapReduce("function(){return 1}", "function(){return 2}");
        Document data = col.aggregate(
            a(
                o("$match", o("_id", new ObjectId(modelId))),
                o("$lookup", o(
                    "from", "form",
                    "localField", "formId",
                    "foreignField", "_id",
                    "as", "form"
                )),
                o("$project", o("bpmData", 1, "xml", 1, "formId", 1, "arrangementData", 1, "form", "$form.form"))
            ).toBson()
        ).first();
        if (data == null) {
            error("查询不到当前的流程模型");
        }
        BpmService bpmService = new BpmService();
        bpmService.modelId = modelId;


        if (data.getList("form", Document.class).size() == 0) {
            error("查询不到流程所对应的表单");
        }
        Document form = data.getList("form", Document.class).get(0);
        bpmService.model = Json.cast(data.get("bpmData"), BpmData.class);
        bpmService.model.ext = new BpmData.Ext();
        Map<String, Map> fieldMap = new HashMap<>();
        for (Map d : form.getList("data", Map.class)) {
            String title = d.get("title").toString();
            fieldMap.put(title, d);
        }
        bpmService.model.ext.fields = fieldMap;
        bpmService.model.ext.template = form.getString("template");
        bpmService.model.ext.xml = data.getString("xml");

        return bpmService;
    }

    public static BpmService ofIns(Document data) {
        BpmService bpmService = new BpmService();
        bpmService.ins = Json.cast(data, BpmInstance.class);
        bpmService.model = bpmService.ins.bpmModel;
        if (bpmService.ins.bpmId != null) {
            bpmService.modelId = bpmService.ins.bpmId.toString();
        }
        if (data.containsKey("childps")) {
            bpmService.childps = data.getList("childps", Document.class);
        }
        //保存oldattrs
        bpmService.ins.oldAttrs(bpmService.ins.attrs);
        return bpmService;
    }

    public static BpmService ofIns(String insId) {
//        Lock.lock(insId);
        try {
            MongoCollection<Document> col = db.getCollection("bpmInstance");
            Document data = col.aggregate(
                a(
                    o("$match", o("_id", new ObjectId(insId))),
                    o("$lookup", o(
                        "from", "bpmInstance",
//                        "localField", "_id",
//                        "foreignField", "parInsId",
                        "let", o(
                            "par_id", "$_id"
                        ),
                        "as", "childps",
                        "pipeline", a(
                            o("$match", o(
                                "$expr", o(
                                    "$eq", a("$parInsId", "$$par_id")
                                )
                            )),
                            o("$project", o(
                                "_id", 1,
                                "bpmName", 1
                            ))
                        )
                    ))
//                    o("$project", o(
//                        "childps._id", 1,
//                        "childps.bpmName", 1
//                    ))
                ).toBson()
            ).first();
            return ofIns(data);
        } catch (Exception e) {
//            Lock.unlock(insId);
            error("无法找到ID为%s的流程", insId);
        }
        return null;
    }


    public Arr checkModel() {

//        class Check {
//            //节点名
//            String nodeName;
//            //部门/岗位/人员
//            String type;
//            //原本应该是的名字
//            String shouldName;
//            //现在的名字
//            String nowName;
//
//            public Check(String nodeName, String type, String shouldName, String nowName) {
//                this.nodeName = nodeName;
//                this.type = type;
//                this.shouldName = shouldName;
//                this.nowName = nowName;
//            }
//
//        }

//        BpmData map = Json.cast(bpmData.get("nodes"), BpmData.class);
//        map.nodes.forEach((k,v) -> {
//            v.
//        });
//        Document nodes = Json.get(bpmData, "nodes");
//        for (String s : nodes.keySet()) {
//            Document node = (Document) nodes.get(s);
//            Document permission = (Document) node.get("permission");
//            if (permission == null) {
//                continue;
//            }
//            List<String> dids = permission.get
//        }
//        Arr<Check> checks = a();
//        Obj os = o();
        Obj ds = o();
        Obj rs = o();
        Obj us = o();
        Arr err = a();
        List<Arr> ospreset = a();
        List<Arr> uspreset = a();
        Obj oscache = o();
        Obj uscache = o();
        for (Map.Entry<String, BpmData.Node> entry : model.nodes.entrySet()) {
            BpmData.Node node = entry.getValue();
            for (Map.Entry<String, String> e : node.dealers.deps.entrySet()) {
//                os.put(e.getKey(), e.getValue());
                ds.put(e.getKey(), e.getValue());
                Arr c = oscache.aa(e.getKey());
                c.add(entry.getValue().name);
            }
            for (Map.Entry<String, String> e : node.dealers.roles.entrySet()) {
//                os.put(e.getKey(), e.getValue());
                rs.put(e.getKey(), e.getValue());
                Arr c = oscache.aa(e.getKey());
                c.add(entry.getValue().name);
            }
            for (Map.Entry<String, String> e : node.dealers.users.entrySet()) {
                us.put(e.getKey(), e.getValue());
                Arr c = uscache.aa(e.getKey());
                c.add(entry.getValue().name);
            }
        }

        //检查是否有没有配置经办人的节点
        for (Map.Entry<String, BpmData.Node> entry : model.nodes.entrySet()) {
            BpmData.Node node = entry.getValue();
            if (node.dealers.users.isEmpty() && node.dealers.roles.isEmpty() && node.dealers.deps.isEmpty() && !node.isEnd()) {
                err.add(String.format("节点【%s】的 处理人配置为空", node.name));
            }
        }

        //检查部门(角色）ID和名字是否符合标准
        if (!ds.isEmpty()) {
//            List<Obj> list = work.getOrgList(os.keySet());
            List<Obj> deplist = work.getDepList(ds.keySet());
//                sqlManager.execute("select id,name from t_org where id in (#join(ids)#)", Obj.class, o("ids", os.keySet()));
            for (Obj obj : deplist) {
                String shouldName = ds.s(obj.s("id"));
                String realName = obj.s("name");
                if (!StrUtil.equals(shouldName, realName)) {
                    ospreset.add(a(obj.s("id"), shouldName, realName));
                }
                ds.remove(obj.s("id"));
            }
            //处理多余的
            for (Map.Entry<String, Object> e : ds.entrySet()) {
                ospreset.add(a(e.getKey(), e.getValue(), null));
//                err.add(String.format("节点【%s】的 处理人配置应为【%s】已不存在", node.name, e.getValue()));
            }

        }
        if(!rs.isEmpty()){
            List<Obj> rolelist = work.getRoleList(rs.keySet());
            for (Obj obj : rolelist) {
                String shouldName = rs.s(obj.s("id"));
                String realName = obj.s("name");
                if (!StrUtil.equals(shouldName, realName)) {
                    ospreset.add(a(obj.s("id"), shouldName, realName));
                }
                rs.remove(obj.s("id"));
            }
            //处理多余的
            for (Map.Entry<String, Object> e : rs.entrySet()) {
                ospreset.add(a(e.getKey(), e.getValue(), null));
//                err.add(String.format("节点【%s】的 处理人配置应为【%s】已不存在", node.name, e.getValue()));
            }
        }

        if (us.size() > 0) {
            List<Obj> list = work.getUserNameList(us.keySet());
//                sqlManager.execute("select id,true_name as name from t_user where id in (#join(ids)#)", Obj.class, o("ids", us.keySet()));
            for (Obj obj : list) {
                String shouldName = us.s(obj.s("id"));
                String realName = obj.s("name");
                if (!StrUtil.equals(shouldName, realName)) {
                    uspreset.add(a(obj.s("id"), shouldName, realName));
//                    err.add(String.format("节点【%s】的 处理人配置应为【%s】，实际为【%s】", node.name, shouldName, realName));
                }
                us.remove(obj.s("id"));
            }
            //处理多余的
            for (Map.Entry<String, Object> e : us.entrySet()) {
                uspreset.add(a(e.getKey(), e.getValue(), null));
//                err.add(String.format("节点【%s】的 处理人配置应为【%s】已不存在", node.name, e.getValue()));
            }
        }


        for (Arr arr : ospreset) {
            String id = (String) arr.get(0);
            List<String> names =  aaa(oscache.a(id)
                .stream()
                .distinct()
                .collect(Collectors.toList()));
            for (String name : names) {
                if (arr.get(2) == null) {
                    err.add(String.format("节点【%s】的 处理人配置应为【%s】已不存在", name, arr.get(1)));
                } else {
                    err.add(String.format("节点【%s】的 处理人配置应为【%s】，实际为【%s】", name, arr.get(1), arr.get(2)));
                }
            }
        }

        for (Arr arr : uspreset) {
            String id = (String) arr.get(0);
            List<String> names = aaa(uscache.a(id)
                .stream()
                .distinct()
                .collect(Collectors.toList()));
            for (String name : names) {
                if (arr.get(2) == null) {
                    err.add(String.format("节点【%s】的 处理人配置应为【%s】已不存在", name, arr.get(1)));
                } else {
                    err.add(String.format("节点【%s】的 处理人配置应为【%s】，实际为【%s】", name, arr.get(1), arr.get(2)));
                }
            }
        }


        //检查字段是否符合原本字段的配置
        if (model.ext != null && model.ext.fields != null) {
            for (Map.Entry<String, BpmData.Node> entry : model.nodes.entrySet()) {
                for (String field : entry.getValue().fields.allFields) {
                    if (!model.ext.fields.containsKey(field)) {
                        err.add(String.format("节点【%s】的 填写字段【%s】已不存在", entry.getValue().name, field));
                    }
                }
                for (String field : entry.getValue().fields.requiredFields) {
                    if (!model.ext.fields.containsKey(field)) {
                        err.add(String.format("节点【%s】的 必填字段【%s】已不存在", entry.getValue().name, field));
                    }
                }
            }
        }


        return err;
    }


    /**
     * 回退到上一节点
     *
     * @param uid
     * @return
     */
    public boolean goBack(String uid, Obj back, String logId) {
        BpmInstance.CurrentNode cNode = loginCurrentNode(uid, logId, false, true);
        if (!canGoBack(uid, cNode.nodeId)) {
            error("无权回退任务");
        }

        back.v("reason", Validate.NotBlank, "原因不能为空");

        //找到当时的节点
//        BpmData.Node node = getNode(back.s("logId"));
        //倒序查找这个节点的save

        BpmData.Node cNodeModel = getNode(cNode.nodeId);

//        BpmInstance.CurrentNode cNode = getCurrent(uid);
        //找到对应的log
        BpmInstance.DataLog log = getLog(back.s("logId", ""));
        BpmData.Node node = getNode(log.nodeId);
        if (node == null) {
            error("查询不到应该回退的节点");
        }

        //重做log
        BpmInstance.DataLog newLog = (BpmInstance.DataLog) ((Obj) Json.parse(Json.stringify(log))).to(BpmInstance.DataLog.class);
        newLog.id = IdUtil.objectId();

        //还原节点
        BpmInstance.CurrentNode newCurrentNode = new BpmInstance.CurrentNode(node, log.backup.mainUsers.keySet(), log.backup.supportUsers.keySet(), newLog);
        ins.currentNodes = a(newCurrentNode);

        //backlog
        BpmInstance.DataLog backlog = BpmInstance.DataLog.createBackLog(cNodeModel, uid, back.s("reason"), newLog.nodeName);
        ins.logs.add(backlog);

        newLog.startTime = newLog.time = newLog.endTime = new Date();
        ins.logs.add(newLog);

        // 待处理人
        if(!newCurrentNode.mainUsers.isEmpty() || !newCurrentNode.supportUsers.isEmpty()){
            ins.todoUser = o();
            if(!newCurrentNode.mainUsers.isEmpty()){
                for(String suid : newCurrentNode.mainUsers.keySet()){
                    ins.todoUser.putAll(o(suid,getUserCacheName(suid)));
                }
            }
            if(!newCurrentNode.supportUsers.isEmpty()){
                for(String suid : newCurrentNode.supportUsers.keySet()){
                    ins.todoUser.putAll(o(suid,getUserCacheName(suid)));
                }
            }
        }
        overUserAdd(newCurrentNode.nodeId);
        for(String main : newCurrentNode.mainUsers.keySet()){
            if(ins.overUser.containsKey(main)){
                ins.overUser.remove(main);
            }
        }

        saveIns("currentNodes", "logs", "todoUser", "overUser");

        //查找上一节点的ID
//        int i = ins.logs.size();
//        String nodeId = null;
//        String lastUid = null;
//        String lastUname = null;
//        BpmInstance.Backup backup = null;
        // 当前节点
//        BpmData.Node currNode = getCurrentNode(uid);
//        BpmInstance.DataLog target = null;
//        while (i-- > 0) {
//            BpmInstance.DataLog log = ins.logs.get(i);
//            if (log.type.equals("submit") && !log.nodeId.equals(currNode.id)) {
//                target = log;
//                break;
//            }
//        }
//        if (target == null) {
//            error("找不到要回退的节点");
//        }
//        BpmData.Node node = getNode(target.nodeId);
//        if (node == null) {
//            error("找不到要回退的节点");
//        }

        // 当前节点
        //todo: 将头发梳成粗鄙郑郑模样
//        BpmInstance.CurrentNode currentNode = new BpmInstance.CurrentNode(node, null, null);
//        currentNode.nodeId = node.id;
//        currentNode.nodeName = node.name;
//        currentNode.mainUsers = target.backup.mainUsers;
//        currentNode.supportUsers = target.backup.supportUsers;

        // 记录log，type为goBack
//        Obj update = o();
//        update.put("currentNodes", a(currentNode));
//        MongoCollection<Document> collection = db.getCollection("bpmInstance");
//        UpdateResult res = collection.updateOne(Filters.eq("_id", ins._id), o("$set", update,
//            "$push", o("logs", o(
//                "id", new ObjectId(),
//                "nodeId", node.id,
//                "msg", String.format("<span style='color:red'>从【%s】回退节点到【%s】</span>", currNode.name, node.name),
//                "time", new Date(),
//                "uid", uid,
//                "uname", getUserCacheName(uid),
//                "type", "goBack",
//                "attributes", a(a("原因", reason))
//            ))).toBson()
//        );

        //发送消息给上一步办理人
        HashSet<String> us = new HashSet<>();
        us.addAll(newCurrentNode.mainUsers.keySet());
        us.addAll(newCurrentNode.supportUsers.keySet());
        String msg = String.format("任务【%s】被退回，原因： %s", ins.bpmName, back.s("reason"));
        Notice.sendSystem(
            us, msg, ins._id.toString()
        );
        if (back.s("sendMobile").equals("1")) {
            MessageSend.send(getPhones(us), msg);
        }

        return true;
    }

    public boolean forceResume(String uid) {
        if (!canForceResume(uid)) {
            error("无权强制恢复任务");
        }
        // 记录日志
        Log.log(Auth.getUid() + "", "恢复强制结束流程 %s，流程id %s", ins.bpmName, ins.id);
        MongoCollection<Document> col = db.getCollection("bpmInstance");
        UpdateResult result = col.updateOne(
            Filters.eq("_id", ins._id),
            o(
                "$set", o(
                    "state", "流转中"
                )
            ).toBson()
        );
        return result.getModifiedCount() > 0;
    }

    public boolean forceEnd(String uid) {
        if (!canForceEnd(uid)) {
            error("无权强制结束任务");
        }
        // 记录日志
        Log.log(Auth.getUid() + "", "强制结束流程 %s，流程id %s", ins.bpmName, ins.id);
        MongoCollection<Document> col = db.getCollection("bpmInstance");
        UpdateResult result = col.updateOne(
            Filters.eq("_id", ins._id),
            o(
                "$set", o(
                    "state", "强制结束"
                )
            ).toBson()
        );
        return result.getModifiedCount() > 0;
    }

    public boolean resume(String uid) {
        if (!canResume(uid)) {
            error("无权恢复任务");
        }
        // 记录日志
        Log.log(Auth.getUid() + "", "恢复暂停流程 %s，流程id %s", ins.bpmName, ins.id);
        MongoCollection<Document> col = db.getCollection("bpmInstance");
        UpdateResult result = col.updateOne(
            Filters.eq("_id", ins._id),
            o(
                "$set", o(
                    "state", "流转中"
                )
            ).toBson()
        );
        return result.getModifiedCount() > 0;
    }

    public boolean pause(String uid) {
        if (!canPause(uid)) {
            error("无权暂停任务");
        }
        // 记录日志
        Log.log(Auth.getUid() + "", "暂停流程 %s，流程id %s", ins.bpmName, ins.id);
        MongoCollection<Document> col = db.getCollection("bpmInstance");
        UpdateResult result = col.updateOne(
            Filters.eq("_id", ins._id),
            o(
                "$set", o(
                    "state", "已暂停"
                )
            ).toBson()
        );
        return result.getModifiedCount() > 0;
    }

    /**
     * 催办
     *
     * @param uid
     * @param msg
     */
    public void urge(String uid, String msg) {
        if (!canUrge(uid)) {
            error("无权催办");
        }
        String curr = "";
        for (BpmInstance.CurrentNode currentNode : ins.currentNodes) {
            for (Map.Entry<String, Object> name : currentNode.mainUsers.entrySet()) {
                curr += name.getValue() + " ";
            }
            for (String s : currentNode.mainUsers.keySet()) {
                String message = String.format("来自流程 %s 的催办消息: %s", ins.id, msg);
                Notice.sendSystem(s, message, ins._id.toString());
                // 发送短信
                MessageSend.send(getPhone(s), message);
            }
        }
        // 记录日志
        Log.log(Auth.getUid() + "", "催办流程 %s，流程id %s，当前经办人 %s", ins.bpmName, ins.id, curr);
    }

    public boolean delete(String uid) {
        if (!canDelete(uid)) {
            error("无权删除");
        }
        // 记录日志
        Log.log(Auth.getUid() + "", "删除流程 %s，流程id %s", ins.bpmName, ins.id);
        MongoCollection<Document> collection = db.getCollection("bpmInstance");
        DeleteResult result = collection.deleteOne(Filters.eq("_id", ins._id));
        return result.getDeletedCount() > 0;
    }

    public boolean delete(String uid, Obj body) {
        List<String> ids = (List<String>) body.get("ids");

        String strId = "";
        for (String id : ids) {
            BpmService bpmService = ofIns(id);
            if (!bpmService.canDelete(uid)) {
                error("无权删除");
            }
            strId += bpmService.ins.id + ",";
        }

        if (null == body) {
            error("请求错误");
        }

        // 记录日志
        Log.log(Auth.getUid() + "", "批量删除流程，流程id %s", StrUtil.isBlank(strId) ? strId : strId.substring(0, strId.length() - 1));
//        String [] idArr = (String[]) body.get("ids");
//        List<String> ids = Arrays.asList(idArr);
        MongoCollection<Document> collection = db.getCollection("bpmInstance");

        DeleteResult result = collection.deleteMany(Filters.in("_id", ids.stream().map(e -> new ObjectId(e)).toArray()));//.collect(Collectors.toList())));
        return result.getDeletedCount() > 0;
    }


    /**
     * 当前用户所在节点是否已经全部会签完毕
     *
     * @param uid
     * @return
     */
    public boolean allSignOver(String uid, String nodeId) {
        BpmInstance.CurrentNode cNode = getCurrent(uid, nodeId);
        BpmData.Node node = getNode(cNode.nodeId);
        //禁止会签
        if (node.disallowCounterSign()) {
            //就当全签完了
            return true;
        }
        if (node.flow.countersign.equals("force") || (node.allowCounterSign() && node.noneSponsor())) {
            //除我以外是否已经全部会签完毕
            long overs = ins.signs.stream()
                .filter(e -> e.nodeId.equals(node.id) && e.over && !e.uid.equals(uid))
                .count();
            HashSet<Object> set = new HashSet<>();
            set.addAll(cNode.mainUsers.keySet());
            set.addAll(cNode.supportUsers.keySet());

            return overs == (set.size() - 1) || overs == (set.size());
        }
        return true;

    }


    public boolean canBackward(String uid) {
        return isSu(uid);
    }


    /**
     * 检查一个用户是否可以发布该流程
     *
     * @param uid
     * @return
     */
    public boolean canPub(String uid) {
        return canDeal(uid, "start");
    }

    /**
     * 是否可以同步到最新的模型
     *
     * @param uid
     * @return
     */
    public boolean canSync(final String uid) {
        return isSu(uid) || isFunc(uid, "sync");
    }

    public boolean canSync(final String uid, Set<String> havaSet) {
        if (null == havaSet ) {
            return false;
        }
        return isSu(uid) || havaSet.contains("sync");
    }

    /**
     * 是否可以处理某些节点
     * 使用model上的字段
     *
     * @param uid
     * @param nodeIds
     * @return
     */
    public boolean canDeal(String uid, BpmInstance.CurrentNode cNode, final boolean containsSupporter) {
        if (cNode == null) {
            return false;
        }
        boolean flag = isRunning();
        if (containsSupporter) {
            flag = flag && (cNode.mainUsers.containsKey(uid) || cNode.supportUsers.containsKey(uid));
        } else {
            flag = flag && cNode.mainUsers.containsKey(uid);
        }
        return flag;
    }

    public boolean canDeal(String uid, String nodeIds) {
        if (ins != null && !isRunning()) {
            return false;
        }
        if (StrUtil.isBlank(uid)) {
            return false;
        }
//        if (!ins.state.equalsIgnoreCase("流转中")) {
//            return false;
//        }
        BpmData.Node node = getNode(nodeIds);
        long count = db.getCollection("user")
            .count(o(
                "id", uid,
                "orgs", o(
                    "$elemMatch", o(
                        "$or", a(
                            o("id", o("$in", node.dealers.deps.keySet()), "type", "DEP"),
                            o("id", o("$in", node.dealers.roles.keySet()), "type", "ROLE")
                        )
                    )
                )
            ).toBson());
//        List<Obj> os = sqlManager.execute(new SQLReady("select oid from t_org_user where uid = ? and otype = 'ROLE' union all select did as oid from t_user_dep where uid = ? and type = 'USER' ", uid, uid), Obj.class);
//        return (os.stream().anyMatch(e -> node.dealers.roles.containsKey(e.s("oid"))) || os.stream().anyMatch(e -> node.dealers.deps.containsKey(e.s("oid"))) || node.dealers.users.containsKey(uid));
        return count > 0 || node.dealers.users.containsKey(uid);
    }


    /**
     * 是否可以处理当前节点
     *
     * @param uid
     * @return
     */
    public boolean canDealCurrent(final String uid) {
        //必须是主办人
        return isRunning() && ins.currentNodes.stream().anyMatch(e -> e.mainUsers.containsKey(uid)/* || e.supportUsers.containsKey(uid)*/);
    }

    /**
     * 并发  是否可处理当前节点
     *
     * @param uid
     * @return
     */
    public boolean canConDealCurrent(final String uid) {
        //必须是主办人
        return isRunning() && ins.currentNodes.stream().anyMatch(e -> e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid));
    }

    /**
     * 当前节点是否已提交过
     */
    public boolean isCurrent(String uid) {
        String nodeId = loginCurrentNode(uid);
        for (BpmInstance.DataLog log : ins.logs) {
            if (StrUtil.equals(nodeId, log.nodeId) && StrUtil.equals(log.type, "submit")) {
                return true;
            }
        }
        return false;
    }

    /**
     * 某个用户是否可以查看当前的这个流程
     *
     * @return
     */
    public boolean canSee(String uid) {
        return isFunc(uid, "see") || isSu(uid) || ins.logs.stream()
            .anyMatch(e -> e.uid.equals(uid)) ||
            ins.currentNodes.stream()
                .anyMatch(e -> e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid));
    }

    /**
     * 是否可以回撤
     */

    public boolean canRetreat(String uid) {
        // 处理人是否为主办人
        if (!isLogCurrent(uid)) {
            return false;
        }

        // 下一处理人是否还未打开任务
        if (ins.logs.stream().filter(ee -> null != ee.backup).anyMatch(e -> e.backup.mainUsers.containsKey(uid) || e.backup.supportUsers.containsKey(uid))) {
            // 根据当前登录人，判定是否有处理过任务，没有返回false，有返回最新一条，取节点id，得到节点配置（是否允许回撤）及下一节点n，判断当前节点是否等于下一节点n，通过当前节点logid 得到logs id相匹配的数据，判断con是否为null，并且流程正在流转，是则返回true
            BpmData.Node node = null;
            for(int i = ins.logs.size()-1;i>0;i--){
                if(ins.logs.get(i).uid.equals(uid)){
                    node = getNode(ins.logs.get(i).nodeId);
                    break;
                }
            }

            // 节点是否允许回撤
            if(!node.config.permission.contains("retreat")){
                return false;
            }

            List<BpmInstance.CurrentNode> nextList = null;
            for(BpmData.NextNode next : node.nextNodes){
                nextList = ins.currentNodes.stream().filter(e -> e.nodeId.equals(next.node)).collect(Collectors.toList());
            }

            for(BpmInstance.CurrentNode next : nextList){
                for(BpmInstance.DataLog log : ins.logs){
                    if(next.logId.equals(log.id) && null == log.confirmTime && isRunning()){
                        return true;
                    }
                }
            }


            /*for (BpmInstance.CurrentNode curr : ins.currentNodes) {


                for (BpmInstance.DataLog log : ins.logs) {


                    BpmData.Node node = new BpmData.Node();
                    //查找上一节点的ID
                    int i = ins.logs.size();
                    while (i-- > 0) {
                        BpmInstance.DataLog logs = ins.logs.get(i);
                        if (logs.type.equals("goRetreat")){
                            return false;
                        }
                        // todo 存在问题
                        if (logs.type.equals("submit") && logs.backup.mainUsers.containsKey(uid)) {
                            node = getNode(logs.nodeId);
                            if (node == null) {
                                return false;
                            }
                            break;
                        }
                    }
                    // 当前审批人是否初次查看流程
                    if (StrUtil.equals(curr.nodeId, log.nodeId) && null == log.confirmTime && log.type.equals("save") && isRunning() && canDeal(uid, node.id) && node.config.permission.contains("retreat")) {
                        return true;
                    }
                }
            }*/
            return false;
        } else {
            //
            return false;
        }
    }

    /**
     * 处理人是否为主办人
     *
     * @param uid
     * @return
     */
    private boolean isLogCurrent(String uid) {
        // 上节点是否登录人提交的
        return upperNode(uid);

        // todo 是否处理过流程 需修改
//        return ins.logs.stream().filter(ee -> null != ee.backup).allMatch(e -> e.backup.mainUsers.containsKey(uid));
    }

    /**
     * 上一节点是否为登录人提交
     *
     * @param uid
     * @return
     */
    private boolean upperNode(String uid) {
        //查找上一节点的ID
        int i = ins.logs.size();
        boolean flag = false;
        while (i-- > 0) {
            BpmInstance.DataLog log = ins.logs.get(i);
            // todo 存在问题
            if (log.type.equals("submit") && log.backup.mainUsers.containsKey(uid)) {
                flag = true;
                break;
            }
        }
        return flag;
    }


    /**
     * @param uid
     * @return
     */
    public void currFirst(String uid, BpmInstance.CurrentNode cNode, BpmData.Node node) {
//        if (cNode == null) {
//            return;
//        }
        // 根据用户获取当前节点
//        BpmData.Node cNodeModel = getCurrentNode(uid, nodeId);
        // 判断当前节点
//        if(null != cNodeModel && !cNodeModel.recipientSponsor()){
//            return;
//        }
        BpmInstance.DataLog log = getLog(cNode.logId);
        if (log == null) {
            return;
        }

        //谁先接受谁主板
        if (node.recipientSponsor() && cNode.mainUsers.size() == 0) {
            if (cNode.supportUsers.containsKey(uid)) {
//                BpmInstance.DataLog log = getLog(cNode.logId);
//                if (log == null) {
//                    return;
//                }
                if (log.type.equals("save")) {
                    log.confirmTime = new Date();
                    recipientFirst(uid, cNode, log);
                    saveIns("currentNodes", "logs");
                }
            }
        }
        if ((cNode.mainUsers.containsKey(uid) || cNode.supportUsers.containsKey(uid)) && StrUtil.equals(cNode.nodeId, log.nodeId) && null == log.confirmTime && log.type.equals("save") && isRunning() && canDeal(uid, node.id) && node.config.permission.contains("retreat")) {

            log.confirmTime = new Date();
            saveIns("logs");
        }


        // 是否为当前审批节点
//        if (ins.currentNodes.stream().anyMatch(e -> e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid))) {
//            for (BpmInstance.CurrentNode curr : ins.currentNodes) {
//                for (BpmInstance.DataLog log : ins.logs) {
//                    // 当前审批人是否初次查看流程
//                    if (StrUtil.equals(curr.nodeId, log.nodeId) && null == log.confirmTime && log.type.equals("save")) {
//                        log.confirmTime = new Date();
//                        BpmData.Node cNodeModel = getCurrentNode(uid, curr.nodeId);
//                        if (null != cNodeModel && cNodeModel.recipientSponsor()) {
//                            recipientFirst(uid, curr, log);
//                        }
//                    }
//                }
//            }
//            uptCurrLogs(ins.currentNodes, ins.logs);
//        }
    }

    /**
     * 修改当前节点，日志
     *
     * @param currentNodes
     * @param logs
     */
    private void uptCurrLogs(List<BpmInstance.CurrentNode> currentNodes, List<BpmInstance.DataLog> logs) {
        Obj set = o();

        set.put("currentNodes", currentNodes);
        set.put("logs", logs);
        MongoCollection<Document> collection = db.getCollection("bpmInstance");
        UpdateResult res = collection.updateOne(
            Filters.eq("_id", ins._id),
            o("$set", set).toBson()
        );
    }

    /**
     * 设置主办人(先接收者为主办人)
     */
    private void recipientFirst(String uid, BpmInstance.CurrentNode curr, BpmInstance.DataLog log) {

        String uname = (String) curr.supportUsers.get(uid);
        if (StrUtil.isNotBlank(uname)) {
            curr.supportUsers.remove(uid);
            curr.mainUsers = o(uid, uname);

            log.uid = uid;
            log.uname = uname;
            log.backup.supportUsers.remove(uid);
            log.backup.mainUsers = o(uid, uname);
        }
    }

    /**
     * 添加批注
     *
     * @param uid
     * @param content
     */
    public void addComment(String uid, String content) {
        if (!canComment(uid)) {
            error("无权填写批注");
        }
        if (StrUtil.isEmpty(content)) {
            error("批注内容不能为空");
        }
        saveComment(uid, content);
    }

    /**
     * 保存批注
     */
    private void saveComment(String uid, String content) {
        List<BpmInstance.Comment> comments = a();
        if (null != ins.comments) {
            comments = ins.comments;
        }
        BpmInstance.Comment com = new BpmInstance.Comment();
        com.uid = uid;
        com.uname = getUserCacheName(uid);
        com.content = content;
        com.created = new Date();
        comments.add(com);

        Obj set = o();

        set.put("comments", comments);
        MongoCollection<Document> collection = db.getCollection("bpmInstance");
        UpdateResult res = collection.updateOne(
            Filters.eq("_id", ins._id),
            o("$set", set).toBson()
        );
    }

    /**
     * 是否可以催办当前任务
     *
     * @param uid
     * @return
     */
    public boolean canUrge(String uid) {
        //发起者或者管理员
        return isRunning() && (uid.equals(ins.pubUid) || isSu(uid) || isFunc(uid, "urge"));
    }

    public boolean canUrge(String uid, Set<String> havaSet) {
        if (null == havaSet ) {
            return false;
        }
        //发起者或者管理员
        return isRunning() && (uid.equals(ins.pubUid) || isSu(uid) || havaSet.contains("urge"));
    }


    /**
     * 是否可以编辑这个流程
     *
     * @param uid
     * @return
     */
    public boolean canEdit(String uid) {
        return !isFinished() && (isSu(uid) || isFunc(uid, "edit"));
    }

    public boolean canEdit(String uid, Set<String> havaSet) {
        if (null == havaSet) {
            return false;
        }
        return !isFinished() && (isSu(uid) || havaSet.contains("edit"));
    }

    /**
     * 是否可以挂起任务
     *
     * @param uid
     * @return
     */
    public boolean canPause(String uid) {

        return isRunning() && (isSu(uid) || isFunc(uid, "pause"));
    }

    public boolean canPause(String uid, Set<String> havaSet) {
        if (null == havaSet) {
            return false;
        }
        return isRunning() && (isSu(uid) || havaSet.contains("pause"));
    }

    /**
     * 是否可以恢复被挂起的任务
     *
     * @param uid
     * @return
     */
    public boolean canResume(String uid) {
        return ins.state.equalsIgnoreCase("已暂停") && (isSu(uid) || isFunc(uid, "resume"));
    }

    public boolean canResume(String uid, Set<String> havaSet) {
        if (null == havaSet) {
            return false;
        }
        return ins.state.equalsIgnoreCase("已暂停") && (isSu(uid) || havaSet.contains("resume"));
    }

    /**
     * 是否可以强制结束任务
     *
     * @param uid
     * @return
     */
    public boolean canForceEnd(String uid) {
        return isRunning() && (isSu(uid) || isFunc(uid, "forceEnd"));
    }

    public boolean canForceEnd(String uid, Set<String> haveSet) {
        if (null == haveSet) {
            return false;
        }
        return isRunning() && (isSu(uid) || haveSet.contains("forceEnd"));
    }

    public boolean canForceResume(String uid) {
        return ins.state.equalsIgnoreCase("强制结束") && (isSu(uid) || isFunc(uid, "forceResume"));
    }

    public boolean canForceResume(String uid, Set<String> haveSet) {
        if (null == haveSet) {
            return false;
        }
        return ins.state.equalsIgnoreCase("强制结束") && (isSu(uid) || haveSet.contains("forceResume"));
    }

    /**
     * 是否可以删除任务
     *
     * @param uid
     * @return
     */
    public boolean canDelete(String uid) {
        return isSu(uid) || isFunc(uid, "del") || ins.logs.size() == 1 && null != ins.currentNodes.stream().filter(e -> e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid)).findFirst().orElse(null);
    }

    public boolean canDelete(String uid, Set<String> haveSet) {
        if (null == haveSet) {
            if(ins.logs.size() == 1 && null != ins.currentNodes.stream().filter(e -> e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid)).findFirst().orElse(null)){
                return true;
            }else{
                return false;
            }
        }

        return isSu(uid) || haveSet.contains("del");
    }


    /**
     * 是否可以上传附件
     *
     * @param uid
     * @return
     */
    public boolean canUpload(String uid, BpmInstance.CurrentNode cNode) {
        if (cNode == null) {
            return false;
        }
        BpmData.Node node = getNode(cNode.nodeId);
        if (node == null) {
            return false;
        }
        return cNode.canDeal(uid) && node.upload();
    }

    public boolean canUpload(String uid, String nodeId) {
        try {
            BpmData.Node node = getCurrentNode(uid, nodeId);
            return canDeal(uid, node.id) && node.upload();
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 是否强制合并
     */
    public boolean canMerge(String uid, BpmInstance.CurrentNode cNode) {
        BpmData.Node node = getNode(cNode.nodeId);//getCurrentNode(uid, nodeId);
        if (node == null) {
            return false;
        }
        return node.flow != null && (StrUtil.equals(node.flow.merge, "force"));
    }

    public boolean canMerge(String uid, String nodeId) {
        BpmData.Node node = getNode(nodeId);//getCurrentNode(uid, nodeId);
        if (node == null) {
            return false;
        }
        return node.flow != null && (StrUtil.equals(node.flow.merge, "force"));
    }

    /**
     * 非强制合并时提交
     *
     * @param uid
     * @param nodeId
     */
    private BpmInstance.CurrentNode notMergeSubmit(String uid, String nodeId, BpmData.Node nextNode, String nextUid, Arr agent) {

        // 获取当前节点信息
        BpmData.Node node = getCurrentNode(uid, nodeId);
        BpmInstance.CurrentNode currentNode = new BpmInstance.CurrentNode();
        if (node != null && ins.currentNodes.size() > 1 && StrUtil.equals(node.flow.concurrent, "unforced")) {
            // 修改当前节点
            currentNode = new BpmInstance.CurrentNode(nextNode, a(StrUtil.isNotBlank(nextUid) ? nextUid : undefined), agent);
        } else {
            currentNode = new BpmInstance.CurrentNode(nextNode, a(StrUtil.isNotBlank(nextUid) ? nextUid : undefined), agent);
        }
        return currentNode;
    }

    /**
     * 是否可以会签
     *
     * @param uid
     * @return
     */
    public boolean canSign(String uid, BpmInstance.CurrentNode cNode) {
        if (cNode == null) {
            return false;
        }
        BpmData.Node node = getNode(cNode.nodeId);
        return canSign(uid, node);
    }

    public boolean canSign(String uid, BpmData.Node node) {
        try {
            boolean canSign = node.flow != null && (StrUtil.equals(node.flow.countersign, "allow") || StrUtil.equals(node.flow.countersign, "force"));
            //可以会签和强制会签
            if (noneIns()) {
                return canSign;
            } else {
                BpmInstance.CurrentNode cNode = getCurrent(uid, node.id);
                return cNode != null && canSign && (cNode.mainUsers.containsKey(uid) || cNode.supportUsers.containsKey(uid)) && (boolean) func(() -> {
                    //要不没有符合条件的节点
                    BpmInstance.Sign sign = ins.signs.stream().filter(e -> StrUtil.equals(e.nodeId, cNode.nodeId) && StrUtil.equals(e.uid, uid)).findFirst().orElse(null);
                    return sign == null || !sign.over;
//                if () {
//                    return true;
//                }
//                //要不有一个且为!over
//                if (ins.signs.stream().anyMatch(e -> StrUtil.equals(e.nodeId, cNode.nodeId) && StrUtil.equals(e.uid, uid) && !e.over)) {
//                    return true;
//                }
//                return false;
                });
            }
        } catch (Exception e) {
            return false;
        }
    }

    public boolean canSign(String uid, String nodeId) {
        BpmData.Node node = getCurrentNode(uid, nodeId);
        return canSign(uid, node);
    }


    /**
     * 是否可以回退
     *
     * @param uid
     * @return
     */
    public boolean canGoBack(String uid, BpmInstance.CurrentNode cNode) {
        if (cNode == null) {
            return false;
        }
        BpmData.Node node = getNode(cNode.nodeId);
        if (node == null) {
            return false;
        }
        return isRunning() && cNode.canDeal(uid) && node.config.permission.contains("allowBack");
    }

    public boolean canGoBack(String uid, String nodeId) {
        BpmData.Node node = getCurrentNode(uid, nodeId);
        if (node == null) {
            return false;
        }
        return isRunning() && canDeal(uid, node.id) && node.config.permission.contains("allowBack");
    }

    /**
     * 将任务更新到最新的状态
     *
     * @return
     */
    public void sync() {
        BpmService service = ofIns(ins._id.toString());
        model = service.model;
        modelId = service.modelId;
        ins = service.ins;
//        arrangementData = service.arrangementData;
//        xml = service.xml;
    }

    /**
     * 回撤
     *
     * @param uid
     */
    public void canToRetreat(final String uid) {
        if (!isLogCurrent(uid)) {
            error("无权调用该功能");
        }

        //查找上一节点的ID
        int i = ins.logs.size();
        // 上一节点
        String lastLogId = "";

        // 回撤目的节点（回撤到哪个节点）
        while (i-- > 0) {
            BpmInstance.DataLog datalog = ins.logs.get(i);
            // todo 存在问题
            if (datalog.type.equals("submit") && datalog.backup.mainUsers.containsKey(uid)) {
                lastLogId = datalog.id;
                break;
            }
        }


        BpmInstance.DataLog log = getLog(lastLogId);
        if (log == null) {
            error("找不到对应的日志");
        }
        BpmData.Node node = getNode(log.nodeId);
        if (node == null) {
            error("找不到对应的节点");
        }
        if (!canRetreat(uid)) {
            error("该节点已不可回撤");
        }
        //重新构造currentNodes
        BpmInstance.CurrentNode cNode = new BpmInstance.CurrentNode(node, log.backup.mainUsers.keySet(), log.backup.supportUsers.keySet(), log);
        ins.currentNodes = a(cNode);


        BpmData.Node lastCurrNode = null;
        if (null != log.nodeId) {
            lastCurrNode = getNode(log.nodeId);
        }


        // 回撤节点
        BpmData.Node nextNode = null;
        Obj reMainUsers = o();
        i = ins.logs.size();
        while (i-- > 0) {
            BpmInstance.DataLog datalog = ins.logs.get(i);
            // todo 存在问题
            if (datalog.type.equals("save")) {
                if (null != cNode) {
                    nextNode = getNode(datalog.nodeId);
                    reMainUsers = datalog.backup.mainUsers;
                    break;
                }
            }
        }
        //节点实例
        BpmInstance.CurrentNode lastNode = getCurrent(uid, log.nodeId);

        // 从后一节点回撤到当前节点
        //创建回撤日志
        BpmInstance.DataLog retreatLog = new BpmInstance.DataLog(nextNode, "goRetreat", uid, String.format("从【%s】回撤节点到【%s】", nextNode.name, cNode.nodeName), a(), a(), lastNode);
        retreatLog.confirmTime = new Date();
        ins.logs.add(retreatLog);


        //创建节点日志
        retreatLog = new BpmInstance.DataLog(lastCurrNode, "save", StrUtil.isBlank(uid) ? "-1" : uid, String.format("【%s】", cNode.nodeName), a(), a(), cNode);
        retreatLog.confirmTime = new Date();
        ins.logs.add(retreatLog);

        //重做属性
        ins.attrs = o();
        for (BpmInstance.DataLog dataLog : ins.logs) {
            for (Object attribute : dataLog.attributes) {
                List attr = (List) attribute;
                try {
                    ins.attrs.put((String) attr.get(0), attr.get(1));
                } catch (Exception e) {
                }
            }
        }
        //重置为可执行
        if (node.isEnd()) {
            ins.state = "已办结";
        } else {
            ins.state = "流转中";
        }


        if(!cNode.mainUsers.isEmpty() || !cNode.supportUsers.isEmpty()){
            ins.todoUser = o();
            if(!cNode.mainUsers.isEmpty()){
                for(String suid : cNode.mainUsers.keySet()){
                    ins.todoUser.putAll(o(suid,getUserCacheName(suid)));
                }
            }
            if(!cNode.supportUsers.isEmpty()){
                for(String suid : cNode.supportUsers.keySet()){
                    ins.todoUser.putAll(o(suid,getUserCacheName(suid)));
                }
            }
        }


        overUserAdd(cNode.nodeId);

        for(String main : cNode.mainUsers.keySet()){
            if(ins.overUser.containsKey(main)){
                ins.overUser.remove(main);
            }
        }
        saveIns("currentNodes", "logs", "attrs", "state","todoUser", "overUser");

        // 回撤发送消息提醒
        if (!reMainUsers.isEmpty()) {
            for (String s : reMainUsers.keySet()) {
                String message = String.format("流程 %s 已被 %s 回撤，请知悉", ins.id, getUserCacheName(uid));
                Notice.sendSystem(s, message, ins._id.toString());
            }
        }
    }


    public void backwards(final String uid, final String logId) {
        if (!canBackward(uid)) {
            error("无权调用该功能");
        }
        BpmInstance.DataLog log = getLog(logId);
        if (log == null) {
            error("找不到对应的日志");
        }
        BpmData.Node node = getNode(log.nodeId);
        if (node == null) {
            error("找不到对应的节点");
        }

        //重新构造currentNodes
        BpmInstance.CurrentNode cNode = new BpmInstance.CurrentNode(node, log.backup.mainUsers.keySet(), log.backup.supportUsers.keySet(), log);
        //删除多余的日志
        int idex = ins.logs.indexOf(log);
        ins.logs = ins.logs.subList(0, idex + 1);
        ins.currentNodes = a(cNode);

        //重做属性
        ins.attrs = o();
        for (BpmInstance.DataLog dataLog : ins.logs) {
            for (Object attribute : dataLog.attributes) {
                List attr = (List) attribute;
                try {
                    ins.attrs.put((String) attr.get(0), attr.get(1));
                } catch (Exception e) {
                }
            }
        }
        //重置为可执行
        if (node.isEnd()) {
            ins.state = "已办结";
        } else {
            ins.state = "流转中";
        }
        saveIns("currentNodes", "logs", "attrs", "state");
    }

    /**
     * 同步最新model
     */
    public void syncModel(final String uid) {
        if (!canSync(uid)) {
            error("无权同步最新模型");
        }
        BpmService service = BpmService.ofModel(ins.bpmId.toString());
        ins.bpmModel = service.model;
        // 记录日志
        Log.log(Auth.getUid() + "", "同步流程 %s，流程id %s", ins.bpmName, ins.id);
        saveIns("bpmModel");
    }


    static Pattern argsPattern = Pattern.compile("\\[\\s*(.+?)\\s*\\]");

    /**
     * 因为没有连接DB2 所以外部数据无效
     * @fixme
     * @param uid
     * @param node
     * @param args
     * @return
     */
    public Obj selectFromDatasource(String uid, BpmData.Node node, Obj args) {
//        if (args == null) {
//            args = o();
//        }
//        String sql = node.getDataSourceSql();
//        Matcher matcher = argsPattern.matcher(sql);
//        StringBuffer sb = new StringBuffer();
//        Arr sqlArgs = a();
//        while (matcher.find()) {
//            matcher.appendReplacement(sb, "?");
//            switch (matcher.group(1)) {
//                case "用户ID":
//                    sqlArgs.add(uid);
//                    break;
//
//                default:
//                    sqlArgs.add(args.get(matcher.group(1)));
//                    break;
//            }
//        }
//        matcher.appendTail(sb);
//        sql = sb.toString().replaceAll("\\\\n", " ");
//        try {
//            List<Obj> list = sqlManager.execute(new SQLReady(sql, sqlArgs.toArray()), Obj.class);
//            if (list.isEmpty()) {
//                return o();
//            }
//            return list.get(0);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
        return o();
    }


    private void addDataSourceAttrs(String uid, BpmData.Node node, Obj args, Obj attrs) {
        if (node == null) {
            return;
        }
        if (args == null) {
            args = o();
        }
        //可以使用已有的字段
        args.putAll(attrs);
        boolean enable = node.enableDataSource();
        boolean force = node.forceDataSource();
        if (enable || force) {
            //数据源取值
            Obj _attrs = selectFromDatasource(uid, node, args);
            _attrs.forEach((k, v) -> {
                if (force) {
                    attrs.put(k, v);
                } else if (enable) {
                    if (StrUtil.isBlankIfStr(attrs.get(k))) {
                        attrs.put(k, v);
                    }
                }
            });
        }
    }

    /**
     * 发布前数据整理
     *
     * @param uid
     * @return
     */
//    public Obj preparePub(String uid, Obj args) {
//        if (!canPub(uid)) {
//            error("你没有权限发布该任务");
//        }
//        BpmData.Node node = getNode("start");
//        Obj attrs = o();
////        addMacroFields(uid, node, attrs);
////        addDataSourceAttrs(uid, node, args, attrs);
//
//        return o(
////            "allFields", model.ext.fields,
////            "formFields", node.fields.allFields,
////            "requiredFields", node.fields.requiredFields,
////            "form", model.ext.template,
////            "attrs", attrs,
////            "signs", a(),
//            "xml", model.ext.xml
////            "current", a(getStartId()),
////            "files", a(),
////                "node", node,
////            "deal", true,
////            "upload", canUpload(uid, node.id),
////            "back", false,
////            "sign", canSign(uid, node.id)
//        );
//    }


    /**
     * 查看详情时，列出可处理当前节点（多个）
     * 选择一个，处理。
     */
    /**
     * 可处理的节点
     */
    public Obj dealCurrent(String uid) {
        if (!canSee(uid)) {
            error("无权查看这个流程");
        }
        //当前所有节点
        List currNodeIds = ins.currentNodes.stream().filter(e -> e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid)).map(e -> {

            Obj obj = o();
            obj.put("nodeId", e.nodeId);
            obj.put("nodeName", e.nodeName);
            return obj;
        }).collect(Collectors.toList());


        // save的节点
        Set<String> saveNodeIds = new HashSet<>();
        for (BpmInstance.DataLog log : ins.logs) {
            if (StrUtil.equals(log.type, "save")) {
                saveNodeIds.add(log.nodeId);
            }
        }

        // submit 的节点
        Set<String> submitNodeIds = new HashSet<>();
        for (BpmInstance.DataLog log : ins.logs) {
            if (StrUtil.equals(log.type, "submit")) {
                submitNodeIds.add(log.nodeId);
            }
        }

        saveNodeIds.removeAll(submitNodeIds);

        List list = new ArrayList();
        for (String node : saveNodeIds) {
            for (Object o : currNodeIds) {
                Obj currNode = (Obj) o;
                if (StrUtil.equals(node, (String) currNode.get("nodeId"))) {
                    Obj obj = o();

                    obj.put("nodeId", currNode.get("nodeId"));
                    obj.put("nodeName", currNode.get("nodeName"));
                    list.add(obj);

                }
            }

        }

        List currNode = ins.currentNodes.stream().filter(e -> e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid)).map(e -> {
            Obj obj = o();
            obj.put("nodeId", e.nodeId);
            obj.put("nodeName", e.nodeName);
            return obj;
        }).collect(Collectors.toList());

        Obj ret = o(
            "deal", canConDealCurrent(uid),
            "nodeIds", list == null ? o() : list,
            "currNode", currNode
        );
        return ret;
    }

    public Obj getInsInfo(String uid, Obj args) {
        if (!canSee(uid)) {
            error("无权查看这个流程");
        }
        String logId = args.s(  "logId", "");

        //如果不传nodeId的情况，查找一个可用的节点
        BpmInstance.CurrentNode cNode = null;
        if (logId.isEmpty()) {
            cNode = ins.currentNodes.stream()
                .filter(e -> e.canDeal(uid))
                .findFirst()
                .orElse(null);
        } else {
            cNode = ins.currentNodes.stream()
                .filter(e -> e.logId.equals(logId))
                .findFirst().orElse(null);
        }
        //如果还是没，默认选第一个
        if (cNode == null) {
            cNode = ins.currentNodes.get(0);
        }


        // 记录日志
//        Log.log(Auth.getUid() + "", "查看流程 %s，流程id %s", ins.bpmName,ins.id);
        BpmData.Node node = null;
        if (cNode != null) {
            node = getNode(cNode.nodeId);
        }

        // 记录审批人第一次查看流程
        currFirst(uid, cNode, node);
//        if (cNode == null) {
//
//        }
//        BpmData.Node currentNode = getCurrentNode(uid, cNode.nodeId);

        //是否有权限查看

        //回溯节点附件
//        List<String> needToDelete = new ArrayList<>();
        List<BpmInstance.AddonFile> files = ins.logs.stream()
            .filter(e -> e.files != null && e.files.size() > 0)
            .flatMap(e -> e.files.stream())
            .distinct()
            .collect(Collectors.toList());

//        files.removeIf(e -> needToDelete.contains(e.id));

        List<BpmInstance.Sign> signs = new ArrayList<>();
        signs.addAll(ins.signs);
        try {
            //本步骤其他人不可见
            if (StrUtil.equals(node.flow.canSeeSign, "hideOnCurrentStep")) {
                //只保留我自己的会签信息
                BpmData.Node finalNode = node;
                signs = signs.stream()
                    .filter(e -> e.nodeId.equals(finalNode.id) && e.uid.equals(uid))
                    .collect(Collectors.toList());
            }
        } catch (Exception e) {
        }

        //倒叙排列
//        signs = signs.stream()
//            .sorted(new Comparator<BpmInstance.Sign>() {
//                @Override
//                public int compare(BpmInstance.Sign o1, BpmInstance.Sign o2) {
//                    return o2.created.compareTo(o1.created);
//                }
//            })
//            .collect(Collectors.toList());
        signs = (List) a($expand, ra(signs).group(e -> e.nodeId).values());
//        signs = aaa(aaa(signs).group(new Arg1Function<BpmInstance.Sign>() {
//            @Override
//            public Object call(BpmInstance.Sign arg) throws Exception {
//                return arg.nodeId;
//            }
//        }).values());
        boolean canToRetreat = canRetreat(uid);

        for (BpmInstance.DataLog log : ins.logs) {
            log.backup = null;
        }

        if (null != node) {
            addDataSourceAttrs(uid, node, args, (ins.attrs));
        }
        List<String> current = new ArrayList<>();

        // 是否为并发节点
//        if(isConcurrent(nodeId)){
//            current = getCurrentNodeIds();
//            canDeal = canDealCurrent(uid) && !isCurrent(uid);
//        }else{
//            current = null != currentNode ? a(currentNode.id) : getCurrentNodeIds();
//            canDeal = canDealCurrent(uid);
//        }

        List<Obj> dealCurrents = ins.currentNodes.stream()
            .map(e -> o("logId", e.logId, "type", "node", "nodeName", e.nodeName))
            .collect(Collectors.toList());
        //子流程
        if (childps != null) {
            for (Document childp : childps) {

                dealCurrents.add(o(
                    "type", "childps",
                    "name", childp.get("bpmName"),
                    "state", isEnd(childp.getObjectId("_id").toString()),
                    "insId", childp.getObjectId("_id").toString()
                ));
            }
        }


        Obj ret = o(
            "allFields", model.ext.fields,
            "formFields", (null != node) ? node.fields.allFields : a(),
            "requiredFields", (null != node) ? node.fields.requiredFields : a(),
            "form", model.ext.template,
            "attrs", ins.attrs,
            "signs", signs,
            "xml", model.ext.xml,
            "current", getCurrentNodeIds(),
            "dealCurrents", dealCurrents,
            "logId", null == cNode ? "" : cNode.logId,
            "deal", canDeal(uid, cNode, false),
            "upload", canUpload(uid, cNode),
            "back", canGoBack(uid, cNode),
            "files", files,
            "logs", ins.logs,
            "sign", canSign(uid, cNode),
            "comment", canComment(uid),
            "comments", null == ins.comments ? a() : ins.comments,
            "retreat", canToRetreat,
            "childprocess", null == node ? a() : node.getAllowedChildNames(),
            "parInsId", null == ins.parInsId ? "" : ins.parInsId.toString(),
            "manual", null == node.children || null == node.children.manual ? "0" : node.children.manual
        );
        return ret;
    }

    /**
     * 流程是否已办结
     *
     * @param id
     * @return
     */
    private Boolean isEnd(String id) {
        boolean isEnd = false;

        if (id.isEmpty()) {
            return isEnd;
        }
        MongoCollection<Document> col = db.getCollection("bpmInstance");
        Document workflows = col.aggregate(a(
            o("$match", o("_id", new ObjectId(id))),
            o(
                "$project", o(
                    "state", 1
                )
            )
        ).toBson()).first();
        if (null == workflows) {
            return isEnd;
        }
        String state = (String) workflows.get("state");
        if (null == state) {
            return isEnd;
        }
        isEnd = StrUtil.equals(state, "已办结") || StrUtil.equals(state, "强制结束") ? true : false;
        return isEnd;
    }


    /**
     * 查询当前流程的详细信息
     *
     * @param uid
     * @return
     */
    /*public Obj getInsInfo(String uid, Obj args) {
        if (!canSee(uid)) {
            error("无权查看这个流程");
        }
        // 记录审批人第一次查看流程
        currFirst(uid);

        // 记录日志
//        Log.log(Auth.getUid() + "", "查看流程 %s，流程id %s", ins.bpmName,ins.id);
        BpmData.Node currentNode = getCurrentNode(uid);
        //是否有权限查看

        //回溯节点附件
//        List<String> needToDelete = new ArrayList<>();
        List<BpmInstance.AddonFile> files = ins.logs.stream()
            .filter(e -> e.files != null && e.files.size() > 0)
            .flatMap(e -> e.files.stream())
            .distinct()
            .collect(Collectors.toList());

//        files.removeIf(e -> needToDelete.contains(e.id));

        List<BpmInstance.Sign> signs = new ArrayList<>();
        signs.addAll(ins.signs);
        if (currentNode != null && currentNode.flow != null) {
            //本步骤其他人不可见
            if (StrUtil.equals(currentNode.flow.canSeeSign, "hideOnCurrentStep")) {
                //只保留我自己的会签信息
                signs = signs.stream()
                    .filter(e -> e.nodeId.equals(currentNode.id) && e.uid.equals(uid))
                    .collect(Collectors.toList());
            }
        }

        //倒叙排列
//        signs = signs.stream()
//            .sorted(new Comparator<BpmInstance.Sign>() {
//                @Override
//                public int compare(BpmInstance.Sign o1, BpmInstance.Sign o2) {
//                    return o2.created.compareTo(o1.created);
//                }
//            })
//            .collect(Collectors.toList());
        signs = aaa(signs).group(new Arg1Function<BpmInstance.Sign>() {
            @Override
            public Object call(BpmInstance.Sign arg) throws Exception {
                return arg.nodeId;
            }
        });
        boolean canToRetreat = canRetreat(uid);

        for (BpmInstance.DataLog log : ins.logs) {
            log.backup = null;
        }

        if(null != currentNode){
            addDataSourceAttrs(uid, currentNode, args, (ins.attrs));
        }

        Obj ret = o(
            "allFields", model.ext.fields,
            "formFields", (null != currentNode) ? currentNode.fields.allFields : a(),
            "requiredFields", (null != currentNode) ? currentNode.fields.requiredFields : a(),
            "form", model.ext.template,
            "attrs", ins.attrs,
            "signs", signs,
            "xml", model.ext.xml,
            "current", null != currentNode ? a(currentNode.id) : getCurrentNodeIds(),
            "deal", canDealCurrent(uid),
            "upload", canUpload(uid),
            "back", canGoBack(uid),
            "files", files,
            "logs", ins.logs,
            "sign", canSign(uid,null != currentNode ? currentNode.id:""),
            "comment",canComment(uid),
            "comments",ins.comments,
            "retreat",canToRetreat
        );
        return ret;
    }*/

    /**
     * 校验所填写的属性是否合法
     *
     * @param uid
     * @param node
     * @param attrs
     */
    public void validateAttrs(String uid, BpmInstance.CurrentNode cNode, Obj attrs, Arr files) {

        //过滤不该我填的属性

        //补充宏字段
//        addMacroFields(uid, node, attrs);
        BpmData.Node node = getNode(cNode.nodeId);
        //子流程未完结
        if (node.hasFinishedChildProcesses()) {
            long count = db.getCollection("bpmInstance").countDocuments(o("parInsId", ins._id, "state", "已办结").toBson());
            if (count == 0) {
                error("至少需要一个已办结的子流程");
            }
        }

        //检查附件
        if (node.forceUpload()) {
            if (files.isEmpty()) {
                error("该节点必须上传附件");
            }
        }

        //强制合并
        if (canMerge(uid, cNode) && ins.currentNodes.size() > 1) {
            error("本步骤为强制合并节点，请等待上一步处理完毕");
        }

        //强制会签
        if (!allSignOver(uid, cNode.nodeId)) {
            error("还有人未完成会签，无法提交");
        }

        //验证必填字段
        for (String requiredField : node.fields.requiredFields) {
            Map<String, String> field = model.ext.fields.get(requiredField);
            if (field == null) {
                continue;
            }
            String val = attrs.s(requiredField, "");
            //如果是图片控件
            if (val.startsWith("[img]")) {
                if (val.contains("/empty.png") || val.contains("sign.png")) {
                    val = "";
                }
            }
            //如果是附件控件
            if (val.startsWith("[annex]")) {
                if (val.equals("[annex][]")) {
                    val = "";
                }
            }
            if (StrUtil.isBlank(val)) {
                error("字段[%s]不能为空", requiredField);
            }
        }
    }

    /**
     * 补充宏控件指向的字段
     *
     * @param uid
     * @param node
     * @param attrs
     */
    private void addMacroFields(String uid, BpmData.Node node, Obj attrs) {
        for (String allField : node.fields.allFields) {
            Map<String, String> field = model.ext.fields.get(allField);
            if (field == null) {
                continue;
            }
            if ("macros".equals(field.get("leipiplugins")) && StrUtil.isBlank(field.get("expression"))) {
                attrs.put(field.get("title"), calMacro(uid, field));
            }
        }
    }


    public Object calMacro(String uid, Map<String, String> node) {
        String expression = node.get("expression");
        if (StrUtil.isBlank(expression)) {
            switch (node.get("orgtype")) {
                case "当前用户姓名":
                    return getUserCacheName(uid);
                case "当前用户ID":
                    return uid;

                case "当前用户部门":
                    List<String> ids = db.getCollection("user")
                        .find(o("id", uid).toBson())
                        .projection(o("orgs", 1).toBson())
                        .into(a())
                        .stream()
                        .flatMap(e -> ooo(e).aa("orgs").stream())
                        .map(e -> ooo(e))
                        .filter(e -> e.ss("type").equals("DEP"))
                        .map(e -> e.ss("id"))
                        .distinct()
                        .collect(Collectors.toList());
                    return db.getCollection("dep")
                        .find(o("id", o("$in", ids)).toBson())
                        .projection(o("name", 1).toBson())
                        .into(a())
                        .stream()
                        .map(e -> e.getString("name"))
                        .collect(Collectors.joining(","));
//                    return sqlManager.execute(new SQLReady("select value(LISTAGG(o.name,','),'') as name from t_user_dep ud inner join t_org o on ud.did = o.id where ud.uid = ? and ud.type = 'USER'", uid), Obj.class)
//                        .stream()
//                        .map(e -> e.s("name"))
//                        .filter(e -> e != null)
//                        .findFirst()
//                        .orElse("");

                case "当前用户完整部门":
                    return ooo(db.getCollection("user")
                        .find(o("id", uid).toBson())
                        .projection(o("orgs", 1).toBson())
                        .first())
                        .aa("orgs")
                        .oa()
                        .stream()
                        .filter(e -> e.ss("type").equals("DEP"))
                        .map(e -> {
                            Obj item = ooo(db.getCollection("dep")
                                .aggregate(ba(
                                    o("$match", o("id", e.ss("id"))),
                                    o("$graphLookup", o(
                                        "from", "dep",
                                        "startWith", "$pid",
                                        "connectFromField", "pid",
                                        "connectToField", "id",
                                        "as", "par"
                                    ))
                                ))
                                .first());
                            if (item.isEmpty()) {
                                return "";
                            }
                            Arr<?> list = item.aa("par");
                            String str = list
                                    .os()
                                    .map(ee -> ee.ss("name"))
                                    .collect(Collectors.joining("-"));
                            return str + "," + item.ss("name");
                        })
                        .filter(StrUtil::isNotBlank)
                        .collect(Collectors.joining(","));
//                    return sqlManager.execute(new SQLReady("select value(LISTAGG(DB2INST1.FUNC_GET_ORG_FULLNAME(ud.did),','),'') as name from t_user_dep ud where ud.uid = ? and ud.type = 'USER'", uid), Obj.class)
//                        .stream()
//                        .map(e -> e.s("name"))
//                        .filter(e -> e != null)
//                        .findFirst()
//                        .orElse("");

                case "当前用户上级部门":
                    return ooo(db.getCollection("user")
                        .find(o("id", uid).toBson())
                        .projection(o("orgs", 1).toBson())
                        .first())
                        .aa("orgs")
                        .oa()
                        .stream()
                        .filter(e -> e.ss("type").equals("DEP"))
                        .map(e -> {
                            //查询该部门的上级部门
                            Obj dep = ooo(db.getCollection("dep")
                                .find(bo("id", e.ss("id")))
                                .projection(bo("pid", 1))
                                .first());
                            Obj pdep = ooo(db.getCollection("dep")
                                .find(bo("id", dep.ss("pid")))
                                .projection(bo("name", 1))
                                .first());
                            return pdep.ss("name");
                        })
                    .filter(StrUtil::isNotBlank)
                    .collect(Collectors.joining(","));
//                    return sqlManager.execute(new SQLReady("select name from t_org where id in (select parent_id from t_org where id in (select ud.did from t_user_dep ud where ud.uid = ? and ud.type = 'USER'))", uid), Obj.class)
//                        .stream()
//                        .map(e -> e.s("name", ""))
//                        .filter(e -> StrUtil.isNotBlank(e))
//                        .collect(Collectors.joining(","));


                case "当前日期+时间":
                    return DateUtil.format(new Date(), "yyyy-MM-dd hh:mm:ss");
                case "当前日期":
                    return DateUtil.format(new Date(), "yyyy-MM-dd");
                case "当前年份":
                    return DateUtil.format(new Date(), "yyyy");
                case "当前时间":
                    return DateUtil.format(new Date(), "hh:mm");
                case "当前星期":
                    LocalDate date = LocalDate.now();
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E");
                    return date.format(formatter);

            }
            return null;
        } else {
            return null;
        }
    }


    /**
     * 现在可以直接创建一个空的流程
     *
     * @param uid
     * @return
     */
    public BpmInstance createBpmInstance(String uid, Obj ext) {
        return createBpmInstance(uid, o(), a(), o(), ext, null, null);
    }

    private BpmInstance createBpmInstance(String uid, Obj data, Arr files, Obj sign, Obj ext, List<BpmData.ChildProcessP2c> p2cs, Obj pAttr) {
        if (ext == null) {
            ext = o();
        }
        Obj allAttrs = o();
        BpmService bpmService = this;
//        BpmService bpmService = BpmService.ofModel(modelId);

        if (!canPub(uid)) {
            error("用户没有权限发布任务");
        }

        BpmData.Node startNode = getNode("start");
        addMacroFields(uid, startNode, data);

        //如果传递了自定义属性
        Obj args = ext.oo("args");
        data.putAll(args);
        //不再校验必填
//        validateAttrs(uid, startNode, data);

        // 开始节点
//        String startNodeId = bpmService.model.start;
//        List<String> qids = bpmService.model.nodes.get(startNodeId).qids;
//        List<String> rids = bpmService.model.nodes.get(startNodeId).rids;
//        List<String> dids = bpmService.model.nodes.get(startNodeId).dids;
//
//        String qid = qids.stream().map(q -> "'" + q + "'").collect(Collectors.joining(","));
//        String rid = rids.stream().map(r -> "'" + r + "'").collect(Collectors.joining(","));
//        String did = dids.stream().map(d -> "'" + d + "'").collect(Collectors.joining(","));
//
//
//        List<Obj> list = sqlManager.execute(new SQLReady(String.format("select uid,utname,pid,pname from t_org_user where (oid in (%s) or oid in (%s) or pid in (%s)) and uid='%s'", qids.isEmpty() ? "-1" : qid, rids.isEmpty() ? "-1" : rid, dids.isEmpty() ? "-1" : did, uid)), Obj.class);
//        List<Obj> list2 = sqlManager.execute(new SQLReady(String.format("select uid,utname,pid,pname from t_org_user where uid='%s'", uid)), Obj.class);
//        list.addAll(list2);

//        List<Long> ql = bpmService.model.nodes.get(startNode).qids;


        Map<String, Map> fields = bpmService.model.ext.fields;
        for (Map.Entry<String, Map> field : fields.entrySet()) {
            allAttrs.put(field.getKey(), "");
        }
        Arr attrsArr = a();
        List<String> allFields = startNode.fields.allFields;
        for (String all : allFields) {
            allAttrs.put(all, data.get(all));
            attrsArr.add(a(all, data.get(all)));
        }
//        String deptName = "";
//        long deptId = 0L;
//        String uName = getUserCacheName(uid);
//        if (list.size() > 0) {
//            for (Obj li : list) {
//                if (null != li.get("pid")) {
//                    deptId = (long) li.get("pid");
//                    deptName = (String) li.get("pname");
//                    uName = (String) li.get("utname");
//                    break;
//                }
//            }
//        }

//        dataLog.id = new ObjectId();
//        dataLog.nodeId = startNode.id;
//        dataLog.nodeName = startNode.name;
//        dataLog.msg = startNode.name;
//        dataLog.time = dataLog.startTime = dataLog.endTime = new Date();
//        dataLog.uid = uid;
//        dataLog.attrs = attrs;
//        dataLog.attributes = attrsArr;
//        dataLog.put("id", new ObjectId());
//        dataLog.put("nodeId", startNode.id);
//        dataLog.put("msg", startNode.name);
//        dataLog.put("time", new Date());
//        dataLog.put("startTime", new Date());
//        dataLog.put("endTime", new Date());
//        dataLog.put("uid", uid);
//        dataLog.put("attrs", attrs);
        if (!canUpload(uid, startNode.id)) {
            files = a();
        }

        BpmInstance.DataLog dataLog = new BpmInstance.DataLog(startNode, "save", uid, startNode.name, attrsArr, files);
//        dataLog.files = files;
//        dataLog.uname = uName;
//        dataLog.type = "save";
//        dataLog.put("files", files);
//        dataLog.put("uname", uName);
//        dataLog.put("type", "save");


        BpmInstance.CurrentNode currentNode = new BpmInstance.CurrentNode(startNode, a(uid), a(), dataLog);


        dataLog.backup = new BpmInstance.Backup(currentNode);
//        JSONObject currentNode = new JSONObject();
//        currentNode.put("nodeId", startNode.id);
//        currentNode.put("nodeName", startNode.name);
//        JSONArray uids = new JSONArray();
//        uids.add(uid);
//        currentNode.put("uids", uids);
//        JSONArray unames = new JSONArray();
//        unames.add(getUserName(uid));
//        currentNode.put("unames", unames);
//        JSONArray currentNodes = new JSONArray();

//        Map<String, String> mainUsers = new HashMap<>();
//        Map<String, String> supportUsers = new HashMap<>();
//        mainUsers.put(uid, getUserName(uid));

//        currentNode.put("mainUsers", mainUsers);
//        currentNode.put("supportUsers", supportUsers);
//        currentNodes.add(currentNode);

        //父字映射, 强制覆盖字段
        if (p2cs != null) {
            for (BpmData.ChildProcessP2c p2c : p2cs) {
                allAttrs.put(p2c.to, pAttr.get(p2c.from));
            }
        }


        MongoCollection<Document> collection = db.getCollection("bpmInstance");
//        BpmInstance ins = new BpmInstance();
        BpmInstance instance = ins = new BpmInstance();
        ins.id = getIncrementId();
        ins.state = "流转中";
        ins.bpmId = new ObjectId(modelId);
        ins.bpmName = model.workflowName;
        ins.pubUid = uid;
        ins.pubUName = dataLog.uname;
        ins.bpmModel = model;
        ins.currentNodes = a(currentNode);
        ins.attrs = allAttrs;
        ins.signs = a();
        ins.logs = a(dataLog);
        ins.createTime = ins.lastModifyTime = new Date();

        ins.todoUser = o(uid, getUserCacheName(uid));
        ins.overUser = o();


        String pid = ext.s("id", "");
        String pLogId = ext.s("logId", "");
        if (!pid.isEmpty() && !pLogId.isEmpty()) {
            //保存父任务ID
            ins.parInsId = new ObjectId(pid);
            ins.parLogId = pLogId;
        }

        String bpmName = ext.s("name", "");
        if (bpmName.isEmpty()) {
            ins.bpmName = model.workflowName + "(" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + ")";
        } else {
            ins.bpmName = bpmName;
        }

//        Obj obj = new Obj();
//        obj.put("id", getIncrementId());
//        obj.put("state", "流转中");
//        obj.put("bpmId", new ObjectId(modelId));
//        obj.put("bpmName", bpmService.model.workflowName);
//        obj.put("pubUid", uid);
//        obj.put("pubUName", uName);
////        obj.put("depId", deptId);
////        obj.put("depName", deptName);
//        obj.put("bpmModel", bpmService.model);
//        obj.put("currentNodes", a(currentNode));

        //流程处理日志
//        BpmInstance.HandleLog handleLog = new BpmInstance.HandleLog();
//        handleLog.startDate = new Date();
//        handleLog.nodeId = startNode.id;
//        handleLog.nodeName = startNode.name;
//        handleLog.uid = uid;
//        handleLog.uname = uName;
//        handleLog.nodeName
//        obj.put("handleLogs", a(handleLog));
//        obj.put("attrs", allAttrs);
//        obj.put("signs", o());
//        //流程提交日志
//        obj.put("logs", a(dataLog));
//        obj.put("createTime", new Date());
//        obj.put("lastModifyTime", new Date());


//        if (canSign(uid, startNode.id)) {
//            saveSign(uid, sign, false, startNode.id);
//        }
        saveIns();

        TriggerService.trigger(this, "EVENT_CREATE", currentNode);

        createChildProcesses(uid, currentNode);
        return ins;
    }


    public BpmInstance.Comment saveComment(final String uid, Obj sign) {
        //检查是否有权限批注
        if (!canComment(uid)) {
            error("无权批注");
        }
        sign.v("content", Validate.NotBlank, "批注内容不能为空");

        BpmInstance.Comment comment = new BpmInstance.Comment();
        comment.uid = uid;
        comment.uname = getUserCacheName(uid);
        comment.created = new Date();
        comment.content = sign.s("content", "");
        comment.x = sign.i("x", 0);
        comment.y = sign.i("y", 0);
        if (ins.comments == null) {
            ins.comments = new ArrayList<>();
        }
        ins.comments.add(comment);
        saveIns("comments");

        // 批注时提醒经办人
        Async.execute(() -> remindComment(comment.uname));

        return comment;
    }

    /**
     * 批注时提醒经办人
     */
    private void remindComment(String uname) {
        BpmService bpmModel = ofModel(ins.bpmId.toString());
        if (bpmModel.model.elseSetting != null) {
            String noticeOnComment = (String) bpmModel.model.elseSetting.get("noticeOnComment");
            if (StrUtil.equals(noticeOnComment, "y")) {
                Set<String> logsUser = ins.logs.stream().filter(e -> e != null && StrUtil.equals(e.type, "submit")).map(ee -> ee.uid).collect(Collectors.toSet());
                //当前的也要取
                for (BpmInstance.CurrentNode cNode : ins.currentNodes) {
                    logsUser.addAll(cNode.mainUsers.keySet());
                    logsUser.addAll(cNode.supportUsers.keySet());
                }
                if (null != logsUser && logsUser.size() > 0) {
                    String msg = String.format("%s 提交了批注，流程id: %s", uname, ins.id);
                    Notice.sendSystem(logsUser, msg, ins._id.toString());
                }
            }
        }
    }

    /**
     * 获取日志中已提交的记录
     * @return
     */
    public void overUserAdd(String nodeId){
        Set<String> overSet = ins.logs.stream().filter(e -> e != null && (StrUtil.equals(e.type, "submit") || StrUtil.equals(e.type, "goback")) && !StrUtil.equals(e.nodeId, nodeId)).map(ee -> ee.uid).collect(Collectors.toSet());
        if(overSet.size()>0 && !overSet.isEmpty()){
            ins.overUser = o();
        }
        for(String suid : overSet){
            /*if(null == ins.overUser || ins.overUser.isEmpty()){
                ins.overUser = o();
            }*/
            ins.overUser.putAll(o(suid, getUserCacheName(suid)));
        }
    }

    public Obj overUserAddAll(String nodeId){
        Set<String> overSet = ins.logs.stream().filter(e -> e != null && StrUtil.equals(e.type, "submit") && !StrUtil.equals(e.nodeId, nodeId)).map(ee -> ee.uid).collect(Collectors.toSet());
        if(overSet.size()>0 && !overSet.isEmpty()){
            ins.overUser = o();
        }
        for(String suid : overSet){
            ins.overUser.putAll(o(suid, getUserCacheName(suid)));
        }
        return ins.overUser;
    }

    /**
     * 保存会签的信息
     *
     * @param uid
     * @param sign
     */
    public void saveSign(String uid, Obj sign, boolean over,  BpmInstance.CurrentNode cNode) {
        BpmData.Node node = getCurrentNode(uid, cNode.nodeId);
        if (sign == null) {
            return;
        }
        //强制会签必须不能为空
        if (node.flow.countersign.equals("force") && over) {
            sign
//                .v("idea", Validate.NotEmpty, "强制会签必须填写意见")
                .v("info", Validate.NotEmpty, "强制会签必须填写意见");
        }
        BpmInstance.Sign _sign = ins.signs.stream()
            .filter(e -> e.nodeId.equals(node.id) && e.uid.equals(uid))
            .findFirst()
            .orElse(null);
        if (_sign == null) {
            _sign = new BpmInstance.Sign();
            ins.signs.add(_sign);
        }
        //如果会签意见是空，当我没会签
        if (StrUtil.isBlank(sign.s("info", "")) && sign.aa("files").isEmpty()) {
            ins.signs.remove(_sign);
            return;
        }
        //过滤掉不合法的附件
        List files = (List) sign.aa("files").stream()
            .filter(e -> ((Obj) e).containsKey("id"))
            .collect(Collectors.toList());

        _sign.nodeId = node.id;
        _sign.nodeName = node.name;
        _sign.uid = uid;
        _sign.uname = getUserCacheName(uid);
        _sign.created = new Date();
        _sign.info = sign.s("info", "");
        _sign.idea = sign.s("idea", "");
        _sign.over = over;
        _sign.files = files;

        ins.overUser.putAll(o(uid, getUserCacheName(uid)));


        // 无主办人会签且所有人会签完毕
        if(over && getNode(cNode.nodeId).noneSponsor() && allSignOver(uid, cNode.nodeId)){
            cNode.mainUsers = o(uid,getUserCacheName(uid));
        }
    }

    /**
     * 获取当前节点信息
     *
     * @param uid
     * @return
     */
    public BpmInstance.CurrentNode getCurrent(String uid, String nodeId) {
        //当前处理的节点
        BpmInstance.CurrentNode currentNode = ins.currentNodes.stream()
            .filter(e -> (e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid)) && StrUtil.equals(e.nodeId, nodeId))
            .findFirst()
            .orElse(null);

        return currentNode;
    }

    /**
     * 通过当前提交的属性查询下一个应该移交的节点
     *
     * @param uid   提交属性的人，必须是当前节点的经办人
     * @param attrs 提交到该任务上的属性
     * @return
     */
    public List<BpmData.Node> getNextNodes(String uid, Obj attrs, String nodeId) {
        //查找所有的属性
        Obj oldAttrs = ooo(ins.attrs);
        if (attrs != null) {
            oldAttrs.putAll(attrs);
        }
        //当前处理的节点
        BpmInstance.CurrentNode currentNode = getCurrent(uid, nodeId);
        if (currentNode == null) {
            error("当前节点查询失败");
        }
        //查找下一个可用的节点
        BpmData.Node node = getNode(currentNode.nodeId);
        if (node == null) {
            error("当前节点查询失败");
        }

        List<BpmData.Node> ret = node.nextNodes.stream()
            .filter(e -> JsEngine.runExpression(oldAttrs, e.expression))
            .map(e -> getNode(e.node))
            .collect(Collectors.toList());

        int limit = 20;
        while (ret.stream().anyMatch(e -> e.id.startsWith("ExclusiveGateway"))) {
            if (limit-- == 0) {
                error("似乎有错误的循环引用");
            }
            ret = ret.stream()
                .flatMap(e -> {
                    if (e.id.startsWith("ExclusiveGateway")) {
                        return e.nextNodes.stream()
                            .filter(ee -> JsEngine.runExpression(oldAttrs, ee.expression))
                            .map(ee -> getNode(ee.node));
                    } else {
                        return Stream.of(e);
                    }
                })
                .collect(Collectors.toList());
        }

//        BpmData.Node target = null;
//        if(node.nextNodes.size() == 0) {
//            error("没有配置下一个流转的节点");
//        } else {
//            //优先判断有表达式的
//            target = node.nextNodes.stream()
//                    .sorted((a,b) -> getExpressionLevel(b.expression).compareTo(getExpressionLevel(a.expression)))
//                    .filter(e -> JsEngine.runExpression(oldAttrs, e.expression))
//                    .map(e -> getNode(e.node))
//                    .findFirst()
//                    .orElse(null);
//        }
//        for (BpmData.NextNode nextNode : node.nextNodes) {
//            //如果表达式位空，则直接使用该节点
//            if(JsEngine.runExpression(oldAttrs, nextNode.expression)){
//                target = getNode(nextNode.node);
//                break;
//            }
////            if (StrUtil.isBlank(nextNode.expression)) {
////                target = getNode(nextNode.node);
////                break;
////            } else if (runExpression(nextNode.expression)) {
////                target = getNode(nextNode.node);
////                break;
////            }
//        }
        if (ret.isEmpty()) {
            error("找不到符合跳转条件的下一节点");
        }
        return ret;
    }

    private Integer getExpressionLevel(String expression) {
        if (StrUtil.isNotBlank(expression)) {
            return 1;
        } else {
            return 0;
        }
    }

    /**
     * 查询下一个节点的可处理人，以本部门的为最优先
     *
     * @param uid
     * @param attrs
     * @return
     */
    public List<Obj> getNextNodePersons(String uid, Obj attrs, BpmInstance.CurrentNode cNode) {
        List<BpmData.Node> nodes = getNextNodes(uid, attrs, cNode.nodeId);
        Map<String, BpmData.Node> bNodes = ins.bpmModel.nodes;

        //查询这个节点所有命中的人
        return nodes.stream()
            .map(target -> {
                SmartChooseOption option = new SmartChooseOption();
                option.currentUserId = uid;
                option.pubUserId = ins.pubUid;
                option.baseUserIds = target.dealers.users.keySet();
                option.baseRoleIds = target.dealers.roles.keySet();
                option.baseDepIds = target.dealers.deps.keySet();
//
//                Obj params = o(
//                    "uid", uid,
//                    "uids", target.dealers.users.isEmpty() ? a(-1) : target.dealers.users.keySet(),
////                            "qids", target.qids.isEmpty() ? a(-1) : target.qids,
//                    "rids", target.dealers.roles.isEmpty() ? a(-1) : target.dealers.roles.keySet(),
//                    "dids", target.dealers.deps.isEmpty() ? a(-1) : target.dealers.deps.keySet()
//                );
//
//                params.put("uid", uid);

                if (target.filterSelfDep()) {
                    //只允许选择本部门经办人
                    option.chooseSelfDep = true;
//                    params.put("self_dep", true);
                } else if (target.filterParallelDep()) {
                    //只允许选择同级部门经办人
                    option.chooseParallelDep = true;
//                    params.put("parallel_dep", true);
                } else if(StrUtil.equals(target.smart.filterRule, "all")){
                    option.chooseAll = true;
                }

                // 不进行自动选择
                if(StrUtil.equals(target.smart.chooseRule, "none")){
                    option.autoNot = true;
                }

                //自动选择流程发起人
                if (StrUtil.equals(target.smart.chooseRule, "publisher")) {
                    option.choosePublisher = true;
//                    params.put("self", ins.pubUid);
                }
                //自动选择本部门主管(暂时只处理当前提交的步骤）
                if (StrUtil.equals(target.smart.chooseRule, "self_manager")) {
                    option.autoChooseSelfManager = true;
//                    params.put("ms", true);
                }
                if (StrUtil.equals(target.smart.chooseRule, "top_manager")) {
                    option.autoChooseTopManager0 = true;
//                    params.put("tms0", true);
                }
                if (StrUtil.equals(target.smart.chooseRule, "top_manager1")) {
                    option.autoChooseTopManager1 = true;
//                    params.put("tms1", true);
                }

                Arr fis = a();
                if (StrUtil.equals(target.smart.chooseRule, "from_form")) {
                    Arr dis = a();
                    Obj nAttrs = ooo(ins.attrs);
                    nAttrs.putAll(attrs);
                    //暂时只能选人
                    for (String formField : target.smart.formFields) {
                        Map fieldConfig = model.ext.fields.get(formField);
                        if (fieldConfig == null) {
                            continue;
                        }
                        String value = nAttrs.s(formField, "");
                        if (StrUtil.isBlank(value)) {
                            continue;
                        }
                        if (value.startsWith("[json]")) {
                            value = value.substring("[json]".length());
                            //这里是个痛苦的问题
                        }
                        String type = (String) fieldConfig.getOrDefault("leipiplugins", "");
                        Obj users = parse(value);
                        option.autoChooseUser = true;
                        if (type.equals("chooseuser")) {
//                            params.put("ff", fis);
                            fis.addAll(users.keySet());
                        } else if (type.equals("choosedep")) {
//                            params.put("ff", fis);
                            dis.addAll(users.keySet());
                        }
                    }
                    if (!dis.isEmpty()) {
                        //拉出所有符合条件的人
                        fis.addAll(
                            work.getManagersFromDeps(aaa(dis))
//                            sqlManager.select("workflow.查找节点人员-选择部门控件", Obj.class, o("dd", dis))
//                                .stream()
//                                .map(e -> e.s("uid"))
//                                .collect(Collectors.toList())
                        );
                    }
                    for(int i = 0;i<fis.size();i++){
                        option.chooseList.add((String) fis.get(i));
                    }
                    if (fis.isEmpty()) {
                        fis.add(-1);
                    }
                }

                List<Obj> dls = work.getNextNodeDealers(option);
//                List<Obj> dls = sqlManager.select("workflow.查找节点人员-新版", Obj.class, params);
                if(option.autoChooseUser){
                    List<Obj> choose = a();
                    for(int i = 0; i< fis.size();i++){
                        for(Obj list : dls){
                            if(list.s("uid").equals(fis.get(i))){
                                choose.add(list);
                            }
                        }
                    }
                    dls= choose;
                }

                boolean isMainUid = true;
                boolean changeAgent = true;
                try {
                    isMainUid = StrUtil.equals("confirm", target.flow.sponsor);
                    if (null != target.flow.changeAgent) {
                        changeAgent = StrUtil.equals("allow", target.flow.changeAgent);
                    }

                } catch (Exception e) {
                }

                // 得到下一步主办人、经办人
                Map<String, List<Obj>> dealer = getDealers(dls, isMainUid);

                List logs = ins.logs.stream().filter(e -> StrUtil.equals(e.nodeId, target.id)).collect(Collectors.toList());

                return o(
                    "nodeId", target.id,
                    "nodeName", target.name,
                    "allowDynamic", target.allowDynamic(),
                    "dealers", dls,
                    "dealer", dealer,
                    "isMainUid", isMainUid,
                    "canChangeAgent", changeAgent,
                    "logs", logs // todo 只需要backup
                );
            })
            .collect(Collectors.toList());
    }

    private Map<String, List<Obj>> getDealers(List<Obj> dls, boolean isMainUid) {
        Map<String, List<Obj>> dealer = new HashMap<>();
        if (null != dls || !dls.isEmpty()) {
            List<Obj> sponsor = new ArrayList<>();
            List<Obj> main = new ArrayList<>();
            for (int i = 0; i < dls.size(); i++) {
                if (i == 0 && isMainUid) {
                    main.add(dls.get(i));
                    continue;
                }
                sponsor.add(dls.get(i));
            }

            dealer.put("main", main);
            dealer.put("sponsor", sponsor);
        }
        return dealer;
    }


    /**
     * 保存会签数据
     *
     * @param uid
     * @param sign
     * @param over
     */
    public String countersign(String uid, Obj sign, boolean over, String nodeId, String logId) {
        if (!canSign(uid, nodeId)) {
            error("无权参与会签");
        }
        //无主办人会签
        BpmInstance.CurrentNode cNode = loginCurrentNode(uid, logId, true);// getCurrent(uid, nodeId);
        BpmData.Node node = getNode(cNode.nodeId);
        saveSign(Auth.getUid(), sign, over, cNode);
        saveIns("currentNodes","signs", "lastModifyTime", "overUser");
        if (getNode(cNode.nodeId).noneSponsor() && allSignOver(uid, nodeId)) {
            return "continue";
        }
        return "ok";
    }

    /**
     * 并发情况，当前节点为多个时，根据登录人获取对应的nodeId
     */
    public String loginCurrentNode(String uid) {
        return ins.currentNodes.stream()
            .filter(e -> (e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid)))
            .map(e -> e.nodeId)
            .findFirst()
            .orElse(null);
    }

    /**
     * @param uid               用户ID
     * @param logId             节点日志ID
     * @param containsSupporter 是否包含经办人
     * @return
     */
    public BpmInstance.CurrentNode loginCurrentNode(final String uid, final String logId, final boolean containsSupporter) {
        return loginCurrentNode(uid, logId, containsSupporter, false);
    }

    public BpmInstance.CurrentNode loginCurrentNode(final String uid, final String logId, final boolean containsSupporter, final boolean throwException) {
        if (logId == null) {
            if (throwException) {
                throw new BpmException("查询不到当前可处理的节点，该任务可能已经变化，请刷新后重试");
            }
            return null;
        }
        BpmInstance.CurrentNode cnode = ins.currentNodes.stream()
            .filter(e -> {
                boolean flag = e.logId.equals(logId);
                if (containsSupporter) {
                    flag = flag && (e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid));
                } else {
                    flag = flag && e.mainUsers.containsKey(uid);
                }
                return flag;
            })
            .findFirst()
            .orElse(null);
        if (cnode == null && throwException) {
            throw new BpmException("查询不到当前可处理的节点，该任务可能已经变化，请刷新后重试");
        }
        return cnode;
    }

    /**
     * 并发节点是否全部提交完毕
     * 当前节点为合并节点
     * false 未全部提交
     */
    /*public boolean nodeAllSubmit(String currNodeId){
        boolean flag = false;
        Map<String, BpmData.Node> nodeMap = ins.bpmModel.nodes;
        for(Map.Entry<String, BpmData.Node> map : nodeMap.entrySet()) {
            // 下一节点大于1，说明是并发
            List<BpmData.NextNode> nextNodes = map.getValue().nextNodes;
            if (nextNodes.size() > 1) {
                // 并发的多个节点
                for (BpmData.NextNode n : map.getValue().nextNodes) {
                    if (nodeMap.get(n.node).nextNodes.size() > 0) {
                        // 判断是否同一节点
                        String cNextNode = nodeMap.get(n.node).nextNodes.get(0).node;
                        if (StrUtil.equals(currNodeId, cNextNode)) {


                            // 检查logs中并发节点是否全提交完毕
                            flag =  ins.logs.stream().filter(e -> StrUtil.equals(e.nodeId, n.node)).allMatch(ee -> null == ee.confirmTime && StrUtil.equals(ee.type, "submit"));

                            if(flag){
                                return false;
                            }
                        }
                    }
                }

            }
        }

        return flag;
    }*/

    /**
     * 所以提交过的节点
     *
     * @return
     */
    private List<String> allSubmit() {
        List<String> list = new ArrayList<>();
        for (BpmInstance.DataLog log : ins.logs) {
            if (StrUtil.equals("submit", log.type)) {
                list.add(log.nodeId);
            }
        }
        return list;
    }

    /**
     * 并发节点未提交数量
     *
     * @param currNodeId
     * @return
     */
    public int nodeNotSubmit(String currNodeId) {
        boolean flag = false;
        List<String> conNodes = new ArrayList<>();

        Map<String, BpmData.Node> nodeMap = ins.bpmModel.nodes;
        for (Map.Entry<String, BpmData.Node> map : nodeMap.entrySet()) {
            // 下一节点大于1，说明是并发
            List<BpmData.NextNode> nextNodes = map.getValue().nextNodes;
            if (nextNodes.size() > 1) {
                // 并发的多个节点
                for (BpmData.NextNode n : nextNodes) {
                    conNodes.add(n.node);
                }
                if (!conNodes.contains(currNodeId)) {
                    conNodes = new ArrayList<>();
                } else {
                    break;
                }

            }
        }

        List<String> allsub = allSubmit();
        conNodes.removeAll(allsub);


        return conNodes.size();
    }

    /**
     * 是否并发节点
     */
    public boolean isConcurrent(String nodeId) {
        Map<String, BpmData.Node> nodeMap = ins.bpmModel.nodes;
        List<String> nodeIds = new ArrayList<>();
        for (Map.Entry<String, BpmData.Node> map : nodeMap.entrySet()) {
            // 下一节点大于1，说明是并发
            List<BpmData.NextNode> nextNodes = map.getValue().nextNodes;
            if (nextNodes.size() > 1) {
                for (BpmData.NextNode n : nextNodes) {
                    nodeIds.add(n.node);
                }
            }
        }

        return nodeIds.contains(nodeId);

    }

    /**
     * 之前是否提交过下一节点
     *
     * @param nodeId
     * @return
     */
    public boolean isBeforeNextNode(String nodeId) {
        Map<String, BpmData.Node> nodeMap = ins.bpmModel.nodes;
        for (Map.Entry<String, BpmData.Node> map : nodeMap.entrySet()) {
            // 下一节点大于1，说明是并发
            List<BpmData.NextNode> nextNodes = map.getValue().nextNodes;
            if (nextNodes.size() > 1) {
                // 并发的多个节点
                for (BpmData.NextNode n : map.getValue().nextNodes) {
                    if (nodeMap.get(n.node).nextNodes.size() > 0) {
                        // 判断是否同一节点
                        String cNextNode = nodeMap.get(n.node).nextNodes.get(0).node;
                        if (StrUtil.equals(nodeId, cNextNode)) {
                            // 检查logs中并发节点是否有创建
                            return ins.logs.stream().filter(e -> StrUtil.equals(e.nodeId, cNextNode) && StrUtil.equals(e.type, "save")).count() == 0;
                        }
                    }
                }

            }
        }

        return false;

    }

    /**
     * 当前的节点是否为集合中最后一个提交
     */
    private boolean isLastOne(String nextNode) {
        long num = 0L;

        for (BpmInstance.CurrentNode node : ins.currentNodes) {
            long overs = ins.logs.stream()
                .filter(e -> e.nodeId.equals(node.nodeId) && StrUtil.equals(e.type, "submit"))
                .count();
            num += overs;
            if (StrUtil.equals(nextNode, node.nodeId)) {
                num += 1;
            }
        }
        return ins.currentNodes.size() - num == 1;
    }

    /**
     * 根据logid得到当前节点
     * @param logId
     * @return
     */
    public BpmInstance.CurrentNode getCurrentLog(String logId){
        return ins.currentNodes.stream()
                .filter(e -> (StrUtil.equals(e.logId, logId)))
                .findFirst()
                .orElse(null);
    }

    /**
     * 保存节点数据
     *
     * @param data
     */
    public boolean saveIns(String uid, Obj data, boolean validate, String mode, Arr files, Obj sign, BpmInstance.CurrentNode cNode) {
        if (files == null) {
            files = a();
        }

//        BpmInstance.CurrentNode cNode = loginCurrentNode(uid, logId, false, true);
        //candeal
        //编辑模式，只验证是否有编辑权限
        if (mode.equals("edit")) {
            if (!canEdit(uid)) {
                error("没有权限编辑该任务");
            }
        }

//            String nodeId = ins.currentNodes.get(0).nodeId;
//        String nodeId = loginCurrentNode(uid);
//        if (StrUtil.equals("", nodeId)) {
//            nodeId = loginCurrentNode(uid);
//        }
        boolean canDeal = canDeal(uid, cNode, false);//canDealCurrent(uid);
        boolean canSign = canSign(uid, cNode.nodeId);
        if (!canDeal && !canSign && !canEdit(uid)) {
            error("用户没有权限处理任务");
        }

        Set keys = new HashSet();
        files = filterFiles(uid, files, cNode.nodeId);

        List<String> allFields = null;

        // 编辑不修改宏字段
        if (canDeal || canEdit(uid)) {
            if (!mode.equals("edit")) {
                addMacroFields(uid, getCurrentNode(uid, cNode.nodeId), data);
            }

            if (validate) {
                validateAttrs(uid, cNode, data, files);
            }

//            BpmService bpmService = this;
            BpmData.Node node = getNode(cNode.nodeId);

//        Map<String, Object> attrs = new HashMap<>();
            if (mode.equalsIgnoreCase("edit")) {
                // 记录日志
                Log.log(Auth.getUid() + "", "编辑流程 %s，流程id %s", ins.bpmName, ins.id);
                //编辑模式下，开放所有字段
                allFields = new ArrayList<>();
                for (Map.Entry<String, Map> entry : ins.bpmModel.ext.fields.entrySet()) {
                    allFields.add(entry.getKey());
                }
            } else {
                //一般模式下， 只处理允许处理的字段
                allFields = getNodeAllFields(cNode.nodeId);
//                allFields = ins.bpmModel.nodes.get(cNode.nodeId).fields.allFields;
//                if (null == allFields) {
//                    allFields = new ArrayList<>();
//                }
//                // 不在填写字段中，必填中的字段默认为可填写字段
//                List<String> reFields = ins.bpmModel.nodes.get(cNode.nodeId).fields.requiredFields;
//                reFields.removeAll(allFields);
//                allFields.addAll(reFields);
            }
            Arr attributes = a();
            for (String all : allFields) {
                String val = data.s(all, "");
                ins.attrs.put(all, val);
                attributes.add(a(all, val));
            }

            BpmInstance.DataLog dataLog = null;//new BpmInstance.DataLog();

//            List<BpmInstance.DataLog> logs = ins.logs;
            //编辑模式下不一定有当前节点
            if (canDeal) {
//                BpmInstance.CurrentNode cNode = getCurrent(uid, nodeId);
                BpmInstance.DataLog log = getLog(cNode);
                if (null != log) {
                    //更新属性
                    log.attributes = attributes;
                    //更新文件
                    log.files = aaa(files);//filterFiles(uid, files, cNode.nodeId);
                    //                if(!canUpload(uid)){
                    //                    files = a();
                    //                }
                    //                //只能保存我自己的的文件
                    //                log.files = (List) files.stream()
                    //                    .filter(e -> ((Obj) e).s("creator", "").equals(uid))
                    //                    .collect(Collectors.toList());
                    log.msg = String.format("【%s】", cNode.nodeName);
                }

            } else {
                //创建新的编辑日志
                BpmInstance.DataLog log = BpmInstance.DataLog.createEditLog(uid, attributes);
                ins.logs.add(log);

            }

//            BpmInstance.DataLog log = getLog()
//            boolean modify = !logs.isEmpty() && StrUtil.equals(logs.get(logs.size() - 1).nodeId, nodeId) && (logs.get(logs.size() - 1).type.equals("save"));
//            //永远是modify
//            if (modify) {
//                // 暂时取最后一条
//                dataLog = logs.get(logs.size() - 1);
//                dataLog.endTime = new Date();
//                if (dataLog.startTime == null) {
//                    dataLog.startTime = new Date();
//                }
//            } else {
//                //新增
//                dataLog = new BpmInstance.DataLog();
//                dataLog.id = IdUtil.objectId();
//                dataLog.nodeId = nodeId;
//                dataLog.nodeName = node.name;
//                //等于上一次的submit时间
//                int i = logs.size();
//                while (i-- > 0) {
//                    BpmInstance.DataLog log = logs.get(i);
//                    if (log.type.equals("submit") || log.type.equals("goBack")) {
//                        dataLog.startTime = log.time;
//                        break;
//                    }
//                }
//                if (dataLog.startTime == null) {
//                    dataLog.startTime = new Date();
//                }
//                dataLog.endTime = new Date();
//                dataLog.uid = uid;
//                dataLog.uname = getUserCacheName(uid);
//                dataLog.backup = new BpmInstance.Backup(getCurrent(uid));
//                ins.logs.add(dataLog);
//            }
//            dataLog.time = new Date();

//            dataLog.attributes = attributes;
//            if (canUpload(uid) && null != files) {
//                //只保留自己上傳的文件
//                dataLog.files = (List) files.stream()
//                    .filter(e -> ((Obj) e).s("creator", "").equals(uid))
//                    .collect(Collectors.toList());
//            }

//            if (mode.equalsIgnoreCase("edit")) {
//                dataLog.msg = "编辑流程";
//                dataLog.type = "edit";
//            } else {
//                if (StrUtil.isNotBlank(node.name)) {
//                    dataLog.msg = String.format("提交【%s】", node.name);
//                }
//                dataLog.type = "save";
//            }

            keys.add("attrs");
            keys.add("logs");
            keys.add("lastModifyTime");
        }

        if (canSign) {
            keys.add("signs");
            keys.add("lastModifyTime");
            keys.add("overUser");
            saveSign(uid, sign, validate, cNode);
        }

        //触发器
        TriggerService.trigger(this, "EVENT_SAVE", cNode);

        return saveIns((String[]) keys.toArray(new String[0]));
    }


    /**
     * 得到某一节点需要填写的所有字段
     *
     * @return
     */
    public List<String> getNodeAllFields(String nodeId) {
        List<String> allFields = new ArrayList<>();
        allFields = ins.bpmModel.nodes.get(nodeId).fields.allFields;
        if (null == allFields) {
            allFields = new ArrayList<>();
        }
        // 不在填写字段中，必填中的字段默认为可填写字段
        List<String> reFields = ins.bpmModel.nodes.get(nodeId).fields.requiredFields;
        reFields.removeAll(allFields);
        allFields.addAll(reFields);
        return allFields;
    }

    /**
     * 触发器相关
     * **************************************************************
     * @param event
     */


    /**
     * ***************************************************************
     * 触发器 END
     */

    public boolean saveIns(String... fields) {
        FieldAccess fa = FieldAccess.get(BpmInstance.class);
        HashSet<String> fs = new HashSet(Arrays.asList(fields));
        if (ins != null) {
            fs.add("lastModifyTime");
            ins.lastModifyTime = new Date();
        }

        List<String> removeTodo = a();

        // 能会签的 add 会签完毕的 remove 属于这些节点已经会签完毕的人
        if(a(fields).contains("currentNodes")){
            ins.todoUser = o();
            for (BpmInstance.CurrentNode currentNode : ins.currentNodes) {
                //
                for(String suid : currentNode.mainUsers.keySet()){

                    BpmData.Node node = getNode(currentNode.nodeId);
                    BpmInstance.Sign ishave = ins.signs.stream()
                            .filter(e -> e.nodeId.equals(node.id) && e.over && e.uid.equals(suid)).findFirst().orElse(null);
                    /*if(node.flow.countersign.equals("force") || (node.allowCounterSign() && node.noneSponsor())){
                        ins.todoUser.putAll(o(suid,getUserCacheName(suid)));
                    }
                    else*/
                        if(null != ishave){
                            if (ins != null && !a(fs).contains("todoUser")) {
                                fs.add("todoUser");
                            }
//                        removeTodo.add(suid);
                    }else {
                        ins.todoUser.putAll(o(suid,getUserCacheName(suid)));
                    }

//                    if(allSignOver(suid, currentNode.nodeId)){
//                        ins.todoUser.putAll(o(suid,getUserCacheName(suid)));
//                    }
                }
                for(String suid : currentNode.supportUsers.keySet()){

                    BpmData.Node node = getNode(currentNode.nodeId);
                    BpmInstance.Sign ishave = ins.signs.stream()
                            .filter(e -> e.nodeId.equals(node.id) && e.over && e.uid.equals(suid)).findFirst().orElse(null);
                    /*if(node.flow.countersign.equals("force") || (node.allowCounterSign() && node.noneSponsor())){
                        ins.todoUser.putAll(o(suid,getUserCacheName(suid)));
                    }
                    else */
                        if(null != ishave){
                            if (ins != null && !a(fs).contains("todoUser")) {
                                fs.add("todoUser");
                            }
//                        ins.todoUser.remove(suid);
//                        removeTodo.add(suid);
                    }else {
                        ins.todoUser.putAll(o(suid,getUserCacheName(suid)));
                    }

//                    if(!allSignOver(suid, currentNode.nodeId)){
//                        ins.todoUser.putAll(o(suid,getUserCacheName(suid)));
//                    }
                }
            }

        }

        /*if(!removeTodo.isEmpty()){
            if (ins != null) {
                fs.add("todoUser");
            }
            for(String uid : removeTodo){
                ins.todoUser.remove(uid);
            }

        }*/

        if (!noneIns()) {
            Obj set = o();
            for (String field : fs) {
                set.put(field, fa.get(ins, field));
            }
            MongoCollection<Document> collection = db.getCollection("bpmInstance");
            UpdateResult res = collection.updateOne(
                Filters.eq("_id", ins._id),
                o("$set", set).toBson()
            );
            return res.getModifiedCount() > 0;
        } else if (ins != null) {
            MongoCollection<Document> collection = db.getCollection("bpmInstance");
            Document doc = (Document) a(ins).toBson().get(0);
            doc.remove("_id");
            collection.insertOne(doc);
            if (doc.containsKey("_id")) {
                ins._id = doc.getObjectId("_id");
                return true;
            }
        }
        return false;
    }


    public void createChildProcesses(String uid, BpmInstance.CurrentNode cNode) {
        createChildProcesses(uid, cNode, null, false);
    }

    public void createChildProcesses(String uid, BpmInstance.CurrentNode cNode, String modelId, boolean manual) {
        BpmData.Node node = getNode(cNode.nodeId);
        if (node == null) {
            return;
        }
        if (manual && !node.manualChildProcesses()) {
            return;
        }
        if (!node.forceChildProcesses() && !manual) {
            return;
        }
        ExecutorService executors = Executors.newCachedThreadPool();
        try {
            for (Map.Entry<String, BpmData.ChildProcessItem> entry : node.children.allows.entrySet()) {
                if (modelId != null && !entry.getKey().equals(modelId)) {
                    continue;
                }
                String id = entry.getKey();
                try {
                    executors.submit(() -> {
                        createChildProcess(uid, id, cNode, entry.getValue());
                    });
                } catch (Exception e) {
                    //忽视掉无法创建的错误
                    e.printStackTrace();
                }
            }
            executors.shutdown();
            executors.awaitTermination(30, TimeUnit.SECONDS);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createChildProcess(String uid, String id, BpmInstance.CurrentNode cNode, BpmData.ChildProcessItem childProcessItem) {
        BpmService service = BpmService.ofModel(id);
        service.createBpmInstance(uid, o(), a(), o(), o("id", ins._id.toString(), "logId", cNode.logId), childProcessItem.p2c, ins.attrs);
    }

    /**
     * 提交节点信息
     *
     * @param uid
     */
//    @Deprecated
//    public Object submitIns(String uid, Obj data, Arr files, Obj sign) {
//        Object bl;
////        if (!canPub(uid)) {
////            error("用户没有权限发布任务");
////        }
//
//        BpmService bpmService = this;
//        String nodeId = bpmService.ins.currentNodes.get(0).nodeId;
//
//        bl = saveIns(uid, data, true, "deal", files, sign, false);
//
//        List<BpmData.Node> nextNodes = getNextNodes(uid, data);
//        //有且只有一个是结束的时候，直接结束
//        if (nextNodes.size() == 1 && isEndId(nextNodes.get(0).id)) {
//            Obj state = o();
//            state.put("state", "已办结");
//            state.put("currentNodes", a());
//            MongoCollection<Document> collection = db.getCollection("bpmInstance");
//            UpdateResult res = collection.updateOne(Filters.eq("_id", bpmService.ins._id), new Document("$set", state.toBson()));
////            bl = res.getModifiedCount()>0;
//            if (res.getModifiedCount() > 0) {
//                bl = "提交成功，该流程已结束！";
//            }
//        }
////        多个的时候让用户选择往哪
////        BpmData.Node nextNode = getNextNode(uid, data);
//
//        // 判断是否为结束节点
////        if(nextNode.id.equals(bpmService.ins.BpmData.end)){
////
////        }
////        if(bpmService.ins.BpmData.end == bpmService.ins.currentNodes.get(0).nodeId){
////            bl = "该流程已完结！";
////        }
//        return bl;
//    }
    private Arr filterFiles(String uid, Arr files, String nodeId) {
        if (files == null) {
            return a();
        }
        if (!canUpload(uid, nodeId)) {
            return a();
        }
        return aaa(files.stream()
            .filter(e -> ((Obj) e).s("creator", "").equals(uid))
            .collect(Collectors.toList()));
    }


    /**
     * 保存选取的下一步处理人
     *
     * @param uid 提交人
     */
    public Object nextApprover(String uid, Obj body, Obj data, Arr files, Obj sign, String logId) {
        BpmService bpmService = this;

        BpmInstance.CurrentNode cNode = loginCurrentNode(uid, logId, true, true);

        //是否可以提交
//        boolean canDeal = cNode.canDeal(uid);//canDealCurrent(uid);
        //是否可以会签
//        boolean canSign = canSign(uid, cNode);
        if (body == null) {
            error("下一步骤参数不能为空");
        }

        //强制会签的情况，即使有主办人也必须要求全部签完
//        if (!allSignOver(uid, cNode.nodeId)) {
//            error("还有人未完成会签，无法提交");
//        }

        //当前节点模型
        BpmData.Node currNode = getNode(cNode.nodeId); //getCurrentNode(uid, nodeId);
        //当前节点实例
//        BpmInstance.CurrentNode cNode = getCurrent(uid, nodeId);

        //是否强制合并
//        boolean canMerge = canMerge(uid, cNode);
//        // 强制合并且其他步骤未处理完毕
//        if (canMerge && ins.currentNodes.size() > 1) {
//            error("本步骤为强制合并节点，请等待其他步骤处理完毕");
//        }

        //强制上传的情况
//        files = filterFiles(uid, files, cNode.nodeId);
//        if (currNode.forceUpload()) {
//            if (null == files || files.isEmpty()) {
//                error("该节点必须上传附件");
//            }
//        }

        Obj nextNodes = body.oo("nextNodes");//.entrySet().
        if (nextNodes.isEmpty()) {
            error("没有跳转的节点");
        }
        if (nextNodes.size() > 1 && !currNode.concurrent()) {
            error("该节点不允许并发");
        }
        Obj created = o();
//        List<BpmInstance.CurrentNode> currentCopy = new ArrayList<>(ins.currentNodes);
        Arr newNextNodes = a();
        for (String nodeId : nextNodes.keySet()) {
            Obj next = nextNodes.o(nodeId);
            BpmData.Node nextNode = getNode(nodeId);
            if (nextNode == null) {
                error("查询不到要提交的节点信息");
            }
            //动态并发
            if (nextNode.allowDynamic()) {
                for (Object suid : next.aa("supportUsers")) {
                    newNextNodes.add(o(
                        "node", nextNode,
                        "mainUsers", a(suid),
                        "supportUsers", a()
                    ));
                }
            } else {
                newNextNodes.add(o(
                    "node", nextNode,
                    "mainUsers", next.aa("mainUsers"),
                    "supportUsers", next.aa("supportUsers")
                ));
            }
        }

        // 当newNextNodes有多个时，只需第一次清空todoUser
        int todoFlag=0;
        for (Object obj : newNextNodes) {
            todoFlag++;
            Obj next = (Obj) obj;
            BpmData.Node nextNode = (BpmData.Node) next.get("node");
            Arr mainUsers = next.aa("mainUsers");
            if (nextNode.noneSponsor()) {
                //主办人必须为空
                if (!mainUsers.isEmpty()) {
                    error("无主办人会签不能指定主办人");
                }
            } else if (nextNode.recipientSponsor()) {
                //主办人必须为空
                if (!mainUsers.isEmpty()) {
                    error("先接收者为主办人不能指定主办人");
                }
            } else if (!mainUsers.isEmpty()) {
                for (Object _mainUser : mainUsers) {
                    String mainUser = String.valueOf(_mainUser);
                    if (!canDeal(mainUser, nextNode.id)) {
                        error("请选择下一步骤操作人员");
                    }
                }
            } else if (nextNode.isEnd()) {

            } else {
                error("请选择下一步骤操作人员");
            }

            String mainUid = "";
            if (!next.aa("mainUsers").isEmpty()) {
                mainUid = String.valueOf(next.aa("mainUsers").get(0));
            }

            //创建转交日志
            String msg = String.format("从【%s】转交节点到【%s】", currNode.name, nextNode.name);
//            Arr agent = next.aa("supportUsers");
//            String agentMsg = "";
//            if (agent != null && agent.size() > 0) {
//                agentMsg = "；经办人：";
//                for (int i = 0; i < agent.size(); i++) {
//                    agentMsg += getUserCacheName(agent.get(i).toString()) + ",";
//                }
//                agentMsg = agentMsg.substring(0, agentMsg.length() - 1);
//                msg += agentMsg;
//            }
            String suers = (String) next.aa("supportUsers")
                .stream()
                .map(e -> getUserCacheName(String.valueOf(e)))
                .collect(Collectors.joining(","));
            BpmInstance.DataLog log = new BpmInstance.DataLog(currNode, "submit", uid, msg, a(suers.isEmpty() ? undefined : a("经办人", suers)), a(), cNode);
            //fixme 反正有问题
            cNode.logId = logId;
            ins.logs.add(log);

            //如果需要跳转的节点已经存在，且不是在当前创建的
            if (ins.currentNodes.stream().anyMatch(e -> e.nodeId.equals(nextNode.id)) && !created.containsKey(nextNode.id)) {
                //只是删除当前节点，不做跳转
//                ins.currentNodes.remove(cNode);
            } else {
                //新增节点
                created.put(nextNode.id, 1);

                BpmInstance.CurrentNode newCurrentNode = new BpmInstance.CurrentNode(nextNode, next.aa("mainUsers"), next.aa("supportUsers"));
                ins.currentNodes.add(newCurrentNode);
                //创建节点日志
                log = new BpmInstance.DataLog(nextNode, "save", next.aa("mainUsers").isEmpty() ? "-1" : mainUid, String.format("【%s】", nextNode.name), a(), a(), newCurrentNode);
                ins.logs.add(log);

                //子流程
                createChildProcesses(mainUid, newCurrentNode);


            }

            if (nextNode.isEnd()) {
                ins.state = "已办结";
            }

            // 待办人员
            if( !next.aa("mainUsers").isEmpty() || !next.aa("supportUsers").isEmpty()){

                if(ins.currentNodes.size()>1 && ins.todoUser.containsKey(uid)){
                    // 当前节点size大于1说明是并发，并发提交时不初始化todoUser
                    ins.todoUser.remove(uid);
                    ins.overUser.putAll(o(uid, getUserCacheName(uid.toString())));
                }else if(todoFlag == 1){
                    ins.todoUser = o();
                }
                if( !next.aa("mainUsers").isEmpty()){
                    for (Object suid : next.aa("mainUsers")) {
                        ins.todoUser.putAll(o(suid,getUserCacheName(suid.toString())));
                    }
                }
                if( !next.aa("supportUsers").isEmpty()){
                    for (Object suid : next.aa("supportUsers")) {
                        ins.todoUser.putAll(o(suid,getUserCacheName(suid.toString())));
                    }
                }
            }

            ins.overUser.putAll(o(uid,getUserCacheName(uid)));
        }

        //fixme 无主办人会签明明有logId，却找不到对应的节点
        BpmInstance.DataLog saveLog = getLog(cNode);
        if (saveLog != null) {
            saveLog.endTime = new Date();
        }

        ins.currentNodes.remove(cNode);

        boolean flag = saveIns("currentNodes", "logs", "state", "attrs", "signs", "todoUser", "overUser");
//            createChildProcesses(ui);
        if (flag) {
            TriggerService.trigger(this, "EVENT_GO_NEXT", cNode);
            if (isFinished()) {
                TriggerService.trigger(this, "EVENT_END", cNode);
                Async.execute(() -> {
                    //回写属性
                    writeBackToParentProcess();
                });
                BpmMQ.endBpm(this.ins);
            }
            sendNotice(body, aaa(newNextNodes));
        }
        return o(
            "ok", flag,
            "end", isFinished()
        );
//        }
    }

    private void writeBackToParentProcess() {
        if (ins.parInsId == null) {
            return;
        }
        BpmService service = ofIns(ins.parInsId.toString());
        //检查是否还在当前节点
        BpmInstance.CurrentNode cNode = service.ins.currentNodes.stream().filter(e -> e.logId.equals(ins.parLogId)).findFirst().orElse(null);
        if (cNode == null) {
            return;
        }
        BpmData.Node node = service.getNode(cNode.nodeId);
        if (node == null) {
            return;
        }
        BpmData.ChildProcessItem config = node.children.allows.get(ins.bpmId.toString());
        //拉出子对父的回写
        for (BpmData.ChildProcessP2c c2p : config.c2p) {
            service.ins.attrs.put(c2p.to, ins.attrs.get(c2p.from));
        }
        service.saveIns("attrs");
    }

//    private Object bingfa(String uid, Obj body, Obj data, Arr files, Obj sign, List<String> nextNodeId, BpmInstance.CurrentNode cNode, BpmData.Node currNode) {
//
//        String nodeId = currNode.id;
//        // 下一节点
//        List<BpmData.Node> nextNodes = new ArrayList<>();
//        if (null != nextNodeId && !nextNodeId.isEmpty()) {
//            for (String nextid : nextNodeId) {
//                nextNodes.add(getNode(nextid));
//            }
//        }
//
//        if (nextNodes == null) {
//            error("查询不到下一个节点");
//        }
//
//        Object bl;
//
//        boolean saveIns = saveIns(uid, data, true, "deal", files, sign, false, cNode);
//        if (!saveIns) {
//            error("数据保存错误");
//        }
//
//        if (null == nextNodeId || nextNodeId.size() == 0 || nextNodeId.isEmpty()) {
//            error("请选择下一节点");
//
//        }
//
//        Arr current = a();
//
//        Obj main = (Obj) body.get("mainList");
//        Obj agent = (Obj) body.get("agentList");
//        boolean flag = false;
//
//        int i = 0;
//        for (BpmData.Node nextNode : nextNodes) {
//
//            Map<String, String> mainMap = (Map<String, String>) main.get(nextNode.id);
//
//            Map<String, String> agentMap = (Map<String, String>) agent.get(nextNode.id);
//
//            Arr agentArr = a();
//            String agentMsg = "";
//            String msg = String.format("从【%s】转交节点到【%s】", currNode.name, nextNode.name);
//
//            if (null != agentMap && !agentMap.isEmpty()) {
//                agentMsg = "；经办人：";
//                for (Map.Entry<String, String> agentEntry : agentMap.entrySet()) {
//                    agentArr.add(agentEntry.getKey());
//                    agentMsg += getUserCacheName(agentEntry.getKey() + "") + ",";
//                }
//                agentMsg = agentMsg.substring(0, agentMsg.length() - 1);
//                msg += agentMsg;
//            }
//            String mainUser = "";
//            if (null != mainMap && !mainMap.isEmpty()) {
//
//                for (Map.Entry<String, String> mainEntry : mainMap.entrySet()) {
//                    // 主办人只有一个
//                    mainUser = mainEntry.getKey();
//                }
//            }
//
//            //无主办人、先接收者为主办人
//            if (nextNode.noneSponsor() || nextNode.recipientSponsor() || currNode.allowDynamic()) {
//
//            } else if (StrUtil.isNotBlank(mainUser)) {
//                // todo ？
//                /*if(!canDeal(mainEntry.getKey(), nextNode.id)){
//                    error("请选择下一步操作人员");
//                }*/
//            } else if (nextNode.isEnd()) {
//                //结束节点可以没有主办人
//            } else {
//                error("请选择下一步操作人员");
//            }
//
//            if (nextNode.isEnd()) {
//                ins.state = "已办结";
//            }
//
//            //查找上一个节点
//            //更新当前节点的对应时间
//            BpmInstance.DataLog saveLog = getLog(cNode);
//            saveLog.endTime = new Date();
//
//
//            //创建转交日志
//
//            BpmInstance.DataLog log = new BpmInstance.DataLog(currNode, "submit", uid, msg, a(), a(), cNode);
//            ins.logs.add(log);
//
//            BpmInstance.CurrentNode currentNode = new BpmInstance.CurrentNode();
//            // 非强制合并
//            if (!canMerge(uid, nodeId)) {
//                currentNode = notMergeSubmit(uid, nodeId, nextNode, mainUser, body.aa("agentList"));
//                if (currentNode.supportUsers.size() > 0 && currentNode.mainUsers.size() > 0) {
//                    current.add(currentNode);
//                    log = new BpmInstance.DataLog(nextNode, "save", StrUtil.isBlank(mainUser) ? "-1" : mainUser, String.format("【%s】", nextNode.name), a(), a(), currentNode);
//                    ins.logs.add(log);
//
//                }
//
//            }
//
//
//            // 是否允许动态并发
//            if (currNode.allowDynamic()) {
//                if (null != agentMap && !agentMap.isEmpty()) {
//                    for (Map.Entry<String, String> entry : agentMap.entrySet()) {
//                        i++;
//                        if (i >= 20) {
//                            error("当前最多可允许添加20个经办人");
//                        }
//                        currentNode = new BpmInstance.CurrentNode(nextNode, a(entry.getKey()), a());
//                        current.add(currentNode);
//                        log = new BpmInstance.DataLog(nextNode, "save", StrUtil.isBlank(mainUser) ? "-1" : mainUser, String.format("【%s】", nextNode.name), a(), a(), currentNode);
//                        ins.logs.add(log);
//
//                    }
//                }
//
//            } else {
//                currentNode = new BpmInstance.CurrentNode(nextNode, a(StrUtil.isNotBlank(mainUser) ? mainUser : undefined), agentArr);
//                current.add(currentNode);
//                log = new BpmInstance.DataLog(nextNode, "save", StrUtil.isBlank(mainUser) ? "-1" : mainUser, String.format("【%s】", nextNode.name), a(), a(), currentNode);
//                ins.logs.add(log);
//
//            }
//
//            createChildProcesses(mainUser, currentNode);
////            //创建节点日志
////            log = new BpmInstance.DataLog(nextNode, "save", StrUtil.isBlank(mainUser) ? "-1" : mainUser, String.format("【%s】", nextNode.name), a(), a(), currentNode);
////            ins.logs.add(log);
//
//        }
//
//        //更新当前节点
//        ins.currentNodes = current;
//
//        flag = saveIns("currentNodes", "logs", "state", "attrs", "signs");
//        if (flag) {
//            sendNotice(body, null);
//        }
//
////        String nextUid = (String) body.get("uids");
//
//
//        return o(
//            "ok", flag,
//            "end", false
//        );
//    }


    private static void error(String errMessage, Object... objects) {
        throw new BpmException(String.format(errMessage, objects));
    }


    /**
     * 当前登录人有权限看到哪些流程
     *
     * @param uid
     * @return
     */
    public Set<ObjectId> workflowSet(String uid) {
        List<String> roles = work.getUserRoleIds(uid);
//        sqlManager.execute(new SQLReady("select oid from T_USER_ORG where uid = ? ", uid), String.class);
        MongoCollection<Document> col = db.getCollection("workflow");
        return col.aggregate(a(
            o("$project", o(
                "_id", o("$toString", "$_id"),
                "permissionSet", 1
            ))
        ).toBson()).into(a())

            .stream().filter(ee -> null != ee.get("permissionSet")
            )
            .map(e -> {
                Document doc = (Document) e;
                Obj obj = o();
                obj.putAll(doc);
//                        Arr o = a();
                String o = "";
                Map<String, String> pSet = (Map<String, String>) doc.get("permissionSet");
                for (Map.Entry<String, String> set : pSet.entrySet()) {
                    if (roles.contains(set.getKey())) {
                        o = (String) doc.get("_id");
                        break;
                    }
                }
                return o;
            })
            .filter(e -> StrUtil.isNotBlank(e))
            .map(e -> new ObjectId(e))
            .collect(Collectors.toSet());
    }

    /**
     * 获取当前登录人所有角色
     *
     * @return
     */
    public List<String> getCurrentRoles(String uid) {
        return work.getUserRoleIds(uid);
//        return sqlManager.execute(new SQLReady("select oid from T_USER_ORG where uid = ? ", uid), String.class);
    }

    /**
     * 流程对应的权限
     */
    public Document getModelSet(String modelId) {

        MongoCollection<Document> collection = db.getCollection("workflow");
        Document workflows = collection.aggregate(a(
            o("$match", o("_id", new ObjectId(modelId))),
            o(
                "$project", o(
                    "permissionSet", 1
                )
            )
        ).toBson()).first();

        if(null == workflows){
            return null;
        }
        Document form = (Document) workflows.get("permissionSet");
        if(null == form){
            return null;
        }

        return form;
    }

    /**
     * 登录人拥有的权限集合
     *
     * @return
     */
    public Set<String> loginSet(List<String> roles) {
        Document modelSet = getModelSet(modelId);
//        System.err.println(modelId);

        Set<String> havaSet = new HashSet<>();
        for (String s : roles) {
            List<String> sets = (List<String>) modelSet.get(s);
            if (null != sets) {
                for (String set : sets) {
                    havaSet.add(set);
                }
            }
        }
        return havaSet;
    }

    /**
     * 是否拥有此权限
     *
     * @param set
     * @return
     */
    public boolean isFuncSet(String set, List<String> roles) {
        Set<String> havaSet = loginSet(roles);
        if (null == havaSet) {
            return false;
        }
        return havaSet.contains(set);
    }

    /**
     * 流程有哪些权限
     *
     * @param uid
     * @return
     */
    public Object workflowSet1(String uid) {
//        Set<String> roles = getUserOrg(uid);
        List<String> roles = work.getUserRoleIds(uid);
//        sqlManager.execute(new SQLReady("select oid from T_USER_ORG where uid = ? ", uid), String.class);
        MongoCollection<Document> col = db.getCollection("workflow");
        return col.aggregate(a(
            o("$project", o(
                "_id", o("$toString", "$_id"),
                "permissionSet", 1
            ))
        ).toBson()).into(a())

            .stream().filter(ee -> null != ee.get("permissionSet")
            )
            .map(e -> {
                Document doc = (Document) e;
                Obj obj = o();
//                    obj.putAll(doc);
                String o = "";
                Map<String, String> pSet = (Map<String, String>) doc.get("permissionSet");
                for (Map.Entry<String, String> set : pSet.entrySet()) {
                    if (roles.contains(set.getKey())) {
                        o = set.getKey();

                        obj.put(doc.get("_id").toString(), set.getValue());
                        break;
                    }
                }
                return obj;
            })
//                .filter(e -> StrUtil.isNotBlank(e))
//                .map(e -> new ObjectId(e))
            .collect(Collectors.toList());
    }


    public List<String> funcSet(List<Obj> set) {
        List<String> perSet = new ArrayList<>();
        for (Obj li : set) {
            Map<String, Object> maps = li;
            for (Map.Entry<String, Object> map : maps.entrySet()) {
                if (StrUtil.equals(map.getKey(), ins.bpmId.toString())) {
                    perSet.addAll((Collection<? extends String>) map.getValue());
                }
            }
        }

        return perSet;
    }

    /**
     * 是否拥有管理权限
     *
     * @param uid      人员
     * @param function 权限
     * @return
     */
    public boolean isFunc(String uid, String function) {

        if (null == ins || StrUtil.isBlank(uid) || StrUtil.isBlank(function)) {
            return false;
        }
        if (StrUtil.equals("see", function)) {
            return true;
        }
        List<Obj> set = (List) workflowSet1(uid);
        List<String> perSet = funcSet(set);
        return perSet.contains(function);
    }

    /**
     * 是否拥有批注权限
     */
    public boolean canComment(String uid) {
        return isFunc(uid, "comment");
    }

    /*private Map<String, Object> queryLastNode(){
        Map<String, Object> resp = new HashMap<>();
        ins.bpmModel.nodes

        return resp;
    }*/


    /**
     * 通过节点ID查询节点
     *
     * @param nodeId start表示开始，end表示结束，其余情况使用ID查询
     * @return
     */
    public BpmData.Node getNode(String nodeId) {
        if (nodeId == null) {
            return null;
        }
        if (nodeId.equals("start")) {
            return model.nodes.get(getStartId());
        } else if (nodeId.equals("end")) {
            return model.nodes.get(getEndId());
        } else {
            return model.nodes.get(nodeId);
        }
    }


    public boolean isRunning() {
        return ins.state.equalsIgnoreCase("流转中");
    }

    public boolean isFinished() {
        return !noneIns() && ("已办结".equalsIgnoreCase(ins.state) || "强制结束".equalsIgnoreCase(ins.state));
    }


    public String getEndId() {
        return model.nodes.keySet()
            .stream()
            .filter(e -> isEndId(e))
            .findFirst()
            .orElse(null);
    }

    public boolean isEndId(String id) {
        return id.startsWith("end");
    }

    public String getStartId() {
        return model.nodes.keySet()
            .stream()
            .filter(e -> isStartId(e))
            .findFirst()
            .orElse(null);
    }

    public boolean isStartId(String id) {
        return id.startsWith("Start");
    }

    /**
     * 没有保存的ins
     *
     * @return
     */
    public boolean noneIns() {
        return ins == null || ins._id == null;
    }

    /**
     * 得到某个用户当前的节点
     *
     * @param uid
     * @return
     */
    public BpmData.Node getCurrentNode(String uid, String nodeId) {
        if (isFinished()) {
            return getNode("end");
        }
        if (noneIns()) {
            return getNode("start");
        }
        String currNodeId = ins.currentNodes.stream()
            .filter(e -> (e.mainUsers.containsKey(uid) || e.supportUsers.containsKey(uid)) && StrUtil.equals(nodeId, e.nodeId))
            .map(e -> e.nodeId)
            .findFirst()
            .orElse(null);
        if (currNodeId == null) {
            return null;
        }
        return getNode(currNodeId);
    }


    /**
     * 根据当前节点找到对应的日志
     *
     * @param currentNode
     * @return
     */
    public BpmInstance.DataLog getLog(BpmInstance.CurrentNode currentNode) {
        if (currentNode == null) {
            return null;
        }
        return ins.logs.stream()
            .filter(e -> StrUtil.equals(e.id, currentNode.logId))
            .findFirst()
            .orElse(null);
    }

    public BpmInstance.DataLog getLog(String logId) {
        if (logId == null) {
            return null;
        }
        return ins.logs.stream()
            .filter(e -> StrUtil.equals(e.id, logId))
            .findFirst()
            .orElse(null);
    }

    public BpmInstance.DataLog getLogs(String nodeId) {
        if (nodeId == null) {
            return null;
        }
        return ins.logs.stream()
            .filter(e -> StrUtil.equals(e.nodeId, nodeId))
            .findFirst()
            .orElse(null);
    }

    public List<String> getCurrentNodeIds() {
//        if (isFinished()) {
//            return (List) a("end");
//        }
        return ins.currentNodes
            .stream()
            .map(e -> e.nodeId)
            .collect(Collectors.toList());
    }

    public List<BpmData.Node> getCurrentNodes() {
        if (isFinished()) {
            return (List) a(getNode("end"));
        }
        return ins.currentNodes
            .stream()
            .map(e -> getNode(e.nodeId))
            .filter(e -> e != null)
            .collect(Collectors.toList());
    }


    public static String getIncrementId() {
        MongoCollection<Document> col = db.getCollection("id");
        Document doc = col.findOneAndUpdate(o().toBson(), o(
            "$inc", o(
                "id", 1
            )
        ).toBson(), new FindOneAndUpdateOptions().upsert(true));
        if (doc == null) {
            return "1";
        }
        return doc.getInteger("id").toString();
    }


    /**
     * 渲染消息模板
     */
//    Pattern msgReg = Pattern.compile("\\[\\s*(.+?)\\s*\\]");
//    public String renderNotice(String notice, String uid){
//        if(StrUtil.isBlank(notice)){
//            return "";
//        }
//        Map<String, Object> attrs = ins.attrs;
//
//        StringBuffer sb = new StringBuffer();
//        Matcher matcher = msgReg.matcher(notice);
//        while(matcher.find()){
//            String str = (String) func(() -> {
//                switch (matcher.group(1)){
//                    case "流水号":
//                        return ins.id;
//
//                    case "流程名":
//                        return ins.bpmName;
//
//                    default:
//                        String val = matcher.group(1);
//                        if(StrUtil.isBlank(val)){
//                            val = "";
//                        }
//                        if(null != attrs.get(val)){
//                            return attrs.get(val);
//                        }else {
//                            return "";
//                        }
////                        return val;
//                }
//            });
//            matcher.appendReplacement(sb, str);
//        }
//        matcher.appendTail(sb);
//        return sb.toString();
//    }

    /**
     * 下一步节点发送通知
     */
    public void sendNotice(Obj body, List<Obj> newNextNodes) {
        if (body.isEmpty() || null == body) {
            return;
        }
//        List<String> nextUidList = (List<String>) body.get("uids");
//        if(nextUidList.size()<0){
//            error("请选择下一步骤操作人员");
//        }
//        String nextUid = (String) body.get("uids");

        BpmService bpmService = this;
        boolean nextStepNotice = body.b("nextStepNotice");
        boolean nextStepShort = body.b("nextStepShort");
        boolean startNotice = body.b("startNotice");
        boolean startShort = body.b("startShort");
        boolean allNotice = body.b("allNotice");
        boolean allShort = body.b("allShort");

        String msg = String.format("您处理过的流程，流程id：%s，流程：%s，%s已提交下一步骤", body.s("bpmId"), body.s("bpmName"), getUserCacheName(Auth.getUid() + ""));

        String message = body.s("message");
        List<String> reUser = (List<String>) body.get("reUser");
        List<String> agentList = (List<String>) body.get("agent");

        //发送消息人员
        Set<String> sendUser = new HashSet<>();

        Set<String> otherUser = new HashSet<>();

        // 并发的情况
//        boolean concurrent = (boolean) body.get("concurrent");
//        if (concurrent) {
//            List<String> nextNodeId = (List<String>) body.get("nodeIds");
//            Obj main = (Obj) body.get("mainList");
//            Obj agent = (Obj) body.get("agentList");
//            List<BpmData.Node> nodeLists = conNodeList(nextNodeId);
//            // 并发各节点主办人经办人
//            Set<String> mainUser = conMainUser(nodeLists, main, agent);
//            Set<String> agentUser = conAgentUser(nodeLists, main, agent);
//            sendUser.addAll(mainUser);
//            sendUser.addAll(agentUser);
//
//            // 发送短信
//            if (nextStepShort) {
//                MessageSend.send(getPhones(mainUser), message);
//            }
//        } else {
        // 下一步骤不管是否选中都默认发送站内信
        for (Obj newNextNode : newNextNodes) {
            Arr mainUsers = newNextNode.aa("mainUsers");
            sendUser.addAll(aaa(mainUsers));
            Arr supportUsers = newNextNode.aa("supportUsers");
            otherUser.addAll(aaa(supportUsers));

//            String msg2 = String.format("您有新的流程需要处理，流程id：%s，流程：%s", body.s("bpmId"), body.s("bpmName"));
//            Notice.sendSystem(mainUsers, msg2, ins._id.toString());
        }

        if (null != reUser && !reUser.isEmpty()) {
            // 指定经办人
            otherUser.addAll(reUser);
        }

//        if (nextStepNotice && StrUtil.isNotBlank(nextUid)) {


        if (startNotice && StrUtil.isNotBlank(bpmService.ins.pubUid)) {
            // 发起人
            otherUser.add(bpmService.ins.pubUid);
        }
        if (allNotice) {
            // 全部经办人
            /*List<BpmInstance.DataLog> logs = bpmService.ins.logs;
            for (BpmInstance.DataLog list : logs) {
                if(StrUtil.equals(list.type,"submit")){
                    otherUser.add(list.uid);
                }
            }*/
            Set<String> logs = bpmService.ins.logs.stream().filter(e -> e != null && StrUtil.equals(e.type, "submit")).map(ee -> ee.uid).collect(Collectors.toSet());
            otherUser.addAll(logs);
        }
        if (null != sendUser && sendUser.size() > 0) {
            Notice.sendSystem(sendUser, message, ins._id.toString());
        }

        if (null != otherUser && otherUser.size() > 0) {
            Notice.sendSystem(otherUser, msg, ins._id.toString());
        }


        if (startShort) {
            // 发起人
            MessageSend.send(getPhone(bpmService.ins.pubUid), msg);
        }
        if (allShort) {
            // 全部经办人
            List<BpmInstance.DataLog> logs = bpmService.ins.logs;
            Set<String> uidList = logs.stream()
                .map(e -> e.uid)
                .filter(e -> e != null)
                .collect(Collectors.toSet());
            MessageSend.send(getPhones(uidList), msg);
        }
    }

    /**
     * 并发时，获取节点
     *
     * @return
     */
    private List<BpmData.Node> conNodeList(List<String> nextNodeId) {
        // 下一节点
        List<BpmData.Node> nextNodes = new ArrayList<>();
        if (null != nextNodeId && !nextNodeId.isEmpty()) {
            for (String nextid : nextNodeId) {
                nextNodes.add(getNode(nextid));
            }
        }
        return nextNodes;
    }

    /**
     * 并发时，获取主办人，经办人
     *
     * @return
     */
    private Set<String> conMainUser(List<BpmData.Node> nextNodes, Obj main, Obj agent) {
        Set<String> res = new HashSet<>();

        for (BpmData.Node nextNode : nextNodes) {

            Map<String, String> mainMap = (Map<String, String>) main.get(nextNode.id);

            if (null != mainMap && !mainMap.isEmpty()) {

                for (Map.Entry<String, String> mainEntry : mainMap.entrySet()) {
                    res.add(mainEntry.getKey());
                }
            }
        }
        return res;
    }

    /**
     * 并发时，获取经办人
     *
     * @return
     */
    private Set<String> conAgentUser(List<BpmData.Node> nextNodes, Obj main, Obj agent) {
        Set<String> res = new HashSet<>();

        for (BpmData.Node nextNode : nextNodes) {

            Map<String, String> agentMap = (Map<String, String>) agent.get(nextNode.id);

            if (null != agentMap && !agentMap.isEmpty()) {
                for (Map.Entry<String, String> agentEntry : agentMap.entrySet()) {
                    res.add(agentEntry.getKey());
                }

            }
        }
        return res;
    }

    private List<String> getPhones(Set<String> uids) {
//        if (uids.isEmpty()) {
//            return a();
//        }
        return work.getPhones(uids);
//        return sqlManager.execute("select distinct phone from t_user where id in (#join(uids)#)", Obj.class, o("uids", uids)).stream()
//            .map(e -> e.s("phone"))
//            .collect(Collectors.toList());
    }

    private String getPhone(String uid) {
        return work.getPhone(uid);
//        String phone = sqlManager.execute(new SQLReady("select phone from t_user where id = ?", uid), Obj.class).stream()
//            .map(e -> e.s("phone"))
//            .findFirst()
//            .orElse(null);
//        return phone;
    }

}
